Arthur Passuello
initial commit
5e1a30c
"""
Mock LLM adapter for testing without external dependencies.
This adapter provides deterministic responses for testing purposes,
eliminating the need for Ollama or other external LLM services.
Architecture Notes:
- Provides consistent test responses with proper citation format
- Supports all required adapter interface methods
- Returns predictable outputs for reliable testing
- No external dependencies or network calls
"""
import logging
from typing import Dict, Any, Optional, Iterator
import random
from .base_adapter import BaseLLMAdapter, LLMError
from ..base import GenerationParams
logger = logging.getLogger(__name__)
class MockLLMAdapter(BaseLLMAdapter):
"""
Mock adapter for testing without external LLM dependencies.
Features:
- Deterministic responses for testing
- Proper citation formatting
- Configurable response patterns
- No external dependencies
- Fast response times
Configuration:
- response_pattern: Type of response to generate (default: "technical")
- include_citations: Whether to include citations (default: True)
- simulate_errors: Whether to simulate errors (default: False)
- fixed_response: Use a fixed response instead of patterns (optional)
"""
# Predefined response patterns for different test scenarios
RESPONSE_PATTERNS = {
"technical": {
"template": "Based on the technical documentation, {topic} is {description}. The implementation details show that {detail} [Document {doc_num}].",
"descriptions": [
"a key component of the system architecture",
"an important feature for performance optimization",
"designed for scalability and reliability",
"implemented using industry best practices"
],
"details": [
"it uses advanced algorithms for efficient processing",
"the modular design allows for easy extension",
"performance benchmarks show significant improvements",
"the architecture supports both local and distributed deployment"
]
},
"simple": {
"template": "{topic} refers to {description} [Document {doc_num}].",
"descriptions": [
"a fundamental concept",
"an essential component",
"a critical feature",
"an important aspect"
],
"details": []
},
"detailed": {
"template": "The query about {topic} can be answered as follows:\n\n{description}\n\nKey points include:\n1. {point1} [Document {doc1}]\n2. {point2} [Document {doc2}]\n3. {point3} [Document {doc3}]\n\nIn conclusion, {conclusion} [Document {doc4}].",
"descriptions": [
"This is a comprehensive topic that requires detailed explanation",
"This involves multiple interconnected components",
"This represents a complex system with various considerations"
],
"points": [
"The architecture is designed for modularity",
"Performance optimization is a key consideration",
"The system supports various configuration options",
"Integration with external services is supported",
"Error handling is comprehensive and robust"
],
"conclusions": [
"the implementation provides a solid foundation",
"the system meets all specified requirements",
"the design ensures future scalability"
]
}
}
def __init__(self,
model_name: str = "mock-model",
response_pattern: str = "technical",
include_citations: bool = True,
simulate_errors: bool = False,
fixed_response: Optional[str] = None,
config: Optional[Dict[str, Any]] = None):
"""
Initialize mock adapter.
Args:
model_name: Mock model name for identification
response_pattern: Type of response pattern to use
include_citations: Whether to include document citations
simulate_errors: Whether to simulate random errors
fixed_response: Use this fixed response if provided
config: Additional configuration
"""
adapter_config = {
'response_pattern': response_pattern,
'include_citations': include_citations,
'simulate_errors': simulate_errors,
'fixed_response': fixed_response,
**(config or {})
}
super().__init__(model_name, adapter_config)
self.response_pattern = adapter_config['response_pattern']
self.include_citations = adapter_config['include_citations']
self.simulate_errors = adapter_config['simulate_errors']
self.fixed_response = adapter_config.get('fixed_response')
logger.info(f"Initialized Mock adapter with pattern '{response_pattern}'")
def _make_request(self, prompt: str, params: GenerationParams) -> Dict[str, Any]:
"""
Generate a mock response without external calls.
Args:
prompt: The prompt to respond to
params: Generation parameters (mostly ignored for mock)
Returns:
Mock response in expected format
"""
# Simulate errors if configured
if self.simulate_errors and random.random() < 0.1:
raise LLMError("Simulated mock error for testing")
# Generate response text
if self.fixed_response:
response_text = self.fixed_response
else:
response_text = self._generate_response(prompt, params)
# Build mock response structure
response = {
"model": self.model_name,
"response": response_text,
"done": True,
"context": [],
"total_duration": 1000000, # 1ms in nanoseconds
"load_duration": 0,
"prompt_eval_count": len(prompt.split()),
"prompt_eval_duration": 500000,
"eval_count": len(response_text.split()),
"eval_duration": 500000
}
return response
def _parse_response(self, response: Dict[str, Any]) -> str:
"""
Extract text from mock response.
Args:
response: Mock response dict
Returns:
Response text
"""
return response.get("response", "")
def _get_provider_name(self) -> str:
"""Return the provider name."""
return "Mock"
def _validate_model(self) -> bool:
"""Mock models are always valid."""
return True
def _supports_streaming(self) -> bool:
"""Mock adapter can simulate streaming."""
return True
def generate_streaming(self, prompt: str, params: GenerationParams) -> Iterator[str]:
"""
Generate a mock streaming response.
Args:
prompt: The prompt to respond to
params: Generation parameters
Yields:
Response chunks
"""
# Get full response
full_response = self._generate_response(prompt, params)
# Simulate streaming by yielding word by word
words = full_response.split()
for i, word in enumerate(words):
if i < len(words) - 1:
yield word + " "
else:
yield word
def _generate_response(self, prompt: str, params: GenerationParams) -> str:
"""
Generate a response based on the configured pattern.
Args:
prompt: The user's prompt
params: Generation parameters
Returns:
Generated response text
"""
# Extract topic from prompt (simple heuristic)
prompt_lower = prompt.lower()
topic = "the requested information"
# Try to extract a more specific topic
if "risc-v" in prompt_lower:
topic = "RISC-V"
elif "pipeline" in prompt_lower:
topic = "pipeline architecture"
elif "memory" in prompt_lower:
topic = "memory management"
elif "what is" in prompt_lower:
# Extract what comes after "what is"
parts = prompt_lower.split("what is")
if len(parts) > 1:
topic = parts[1].strip().rstrip("?").strip()
# Get pattern configuration
pattern_config = self.RESPONSE_PATTERNS.get(
self.response_pattern,
self.RESPONSE_PATTERNS["technical"]
)
# Generate response based on pattern
if self.response_pattern == "simple":
description = random.choice(pattern_config["descriptions"])
response = pattern_config["template"].format(
topic=topic,
description=description,
doc_num=random.randint(1, 5) if self.include_citations else ""
)
elif self.response_pattern == "detailed":
description = random.choice(pattern_config["descriptions"])
points = random.sample(pattern_config["points"], 3)
conclusion = random.choice(pattern_config["conclusions"])
response = pattern_config["template"].format(
topic=topic,
description=description,
point1=points[0],
point2=points[1],
point3=points[2],
conclusion=conclusion,
doc1=1 if self.include_citations else "",
doc2=2 if self.include_citations else "",
doc3=3 if self.include_citations else "",
doc4=4 if self.include_citations else ""
)
else: # technical (default)
description = random.choice(pattern_config["descriptions"])
detail = random.choice(pattern_config["details"])
response = pattern_config["template"].format(
topic=topic,
description=description,
detail=detail,
doc_num=random.randint(1, 5) if self.include_citations else ""
)
# Remove citation markers if not including citations
if not self.include_citations:
response = response.replace(" [Document ]", "").replace(" []", "")
return response
def get_model_info(self) -> Dict[str, Any]:
"""
Get mock model information.
Returns:
Model info dict
"""
info = super().get_model_info()
info.update({
'is_mock': True,
'response_pattern': self.response_pattern,
'include_citations': self.include_citations,
'simulate_errors': self.simulate_errors
})
return info