chore: import upstream snapshot with attribution
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:08:55 +08:00
commit 7a0da7932b
2985 changed files with 1049377 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
# LLM Integration Examples
This directory contains examples of integrating custom LangChain LLMs with Local Deep Research.
## Examples
### 1. Basic Custom LLM (`basic_custom_llm.py`)
Shows the simplest way to create and use a custom LLM with LDR.
### 2. Advanced Custom LLM (`advanced_custom_llm.py`)
Demonstrates advanced features like:
- Factory functions with configuration
- Multiple LLM registration
- Combining with custom retrievers
- Error handling and retry logic
### 3. Fine-tuned Model Integration (`finetuned_model_example.py`)
Example of using a fine-tuned model for domain-specific research.
### 4. Mock LLM for Testing (`mock_llm_example.py`)
Shows how to create mock LLMs for testing your research pipelines without API costs.
### 5. Rate-Limited Wrapper (`rate_limited_llm.py`)
Demonstrates wrapping any LLM with rate limiting to avoid API limits.
## Running the Examples
1. Install Local Deep Research:
```bash
pip install local-deep-research
```
2. Run an example:
```bash
python examples/llm_integration/basic_custom_llm.py
```
## Key Concepts
- **BaseChatModel**: All custom LLMs must inherit from `langchain_core.language_models.BaseChatModel`
- **Factory Functions**: Can be used to create LLMs with dynamic configuration
- **Registration**: LLMs are registered via the `llms` parameter in API functions
- **Provider Selection**: Use the registered name as the `provider` parameter
## Common Use Cases
1. **Fine-tuned Models**: Use models trained on your specific domain
2. **Custom Wrappers**: Add logging, retry logic, or preprocessing
3. **Mock Testing**: Test research flows without real LLM calls
4. **Rate Limiting**: Manage API quotas effectively
5. **Multi-Model Pipelines**: Use different models for different research phases
@@ -0,0 +1,337 @@
"""
Advanced example of custom LLM integration with Local Deep Research.
This example demonstrates:
- Factory functions with configuration
- Error handling and retry logic
- Combining multiple LLMs
- Integration with custom retrievers
"""
import time
from typing import Any, Dict, List, Optional
from langchain_core.callbacks import CallbackManagerForLLMRun
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import AIMessage, BaseMessage
from langchain_core.outputs import ChatGeneration, ChatResult
from loguru import logger
from local_deep_research.api import (
create_settings_snapshot,
detailed_research,
quick_summary,
)
class RetryLLM(BaseChatModel):
"""LLM wrapper that adds retry logic to any base LLM."""
base_llm: BaseChatModel
max_retries: int = 3
retry_delay: float = 1.0
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> ChatResult:
"""Generate with retry logic."""
last_error = None
delay = self.retry_delay
for attempt in range(self.max_retries):
try:
return self.base_llm._generate(
messages, stop, run_manager, **kwargs
)
except Exception as e:
last_error = e
if attempt < self.max_retries - 1:
logger.warning(
f"Attempt {attempt + 1} failed, retrying in {delay}s..."
)
time.sleep(delay)
delay *= 2 # Exponential backoff
raise last_error
@property
def _llm_type(self) -> str:
return f"retry_{self.base_llm._llm_type}"
class ConfigurableLLM(BaseChatModel):
"""LLM that can be configured with custom parameters."""
model_name: str = "configurable-v1"
response_style: str = "technical"
max_length: int = 500
include_confidence: bool = False
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> ChatResult:
"""Generate response based on configuration."""
# Extract the query
query = messages[-1].content if messages else "No query"
# Build response based on style
if self.response_style == "technical":
response = (
f"Technical Analysis ({self.model_name}): {query[:100]}..."
)
elif self.response_style == "simple":
response = (
f"Simple Answer: Based on the query about {query[:50]}..."
)
else:
response = f"Response: Processing '{query[:50]}...'"
# Limit length
response = response[: self.max_length]
# Add confidence if requested
if self.include_confidence:
response += "\n\nConfidence: High" # Use descriptive confidence instead of hardcoded percentage
message = AIMessage(content=response)
generation = ChatGeneration(message=message)
return ChatResult(generations=[generation])
@property
def _llm_type(self) -> str:
return "configurable"
_DOMAIN_KNOWLEDGE: Dict[str, List[str]] = {
"medical": ["diagnosis", "treatment", "symptoms", "medications"],
"legal": ["contracts", "liability", "regulations", "compliance"],
"technical": ["algorithms", "architecture", "performance", "scalability"],
"finance": ["investments", "risk", "portfolio", "markets"],
}
class DomainExpertLLM(BaseChatModel):
"""LLM that specializes in specific domains."""
domain: str = "general"
expertise_level: float = 0.8
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> ChatResult:
"""Generate domain-specific response."""
query = messages[-1].content if messages else ""
# Check if query matches domain
domain_terms = _DOMAIN_KNOWLEDGE.get(self.domain, [])
relevance = sum(
1 for term in domain_terms if term.lower() in query.lower()
)
if relevance > 0:
response = f"[{self.domain.upper()} EXPERT - High Relevance]: "
else:
response = f"[{self.domain.upper()} EXPERT - General]: "
response += f"Based on my {self.domain} expertise (level: {self.expertise_level}), "
response += f"regarding '{query[:100]}...': This requires specialized knowledge."
message = AIMessage(content=response)
generation = ChatGeneration(message=message)
return ChatResult(generations=[generation])
@property
def _llm_type(self) -> str:
return f"expert_{self.domain}"
def create_configured_llm(config: Dict[str, Any]) -> BaseChatModel:
"""Factory function that creates LLMs based on configuration."""
llm_type = config.get("type", "basic")
if llm_type == "retry":
# Create base LLM first
base_config = config.get("base_config", {})
base_llm = create_configured_llm(base_config)
# Wrap with retry
return RetryLLM(
base_llm=base_llm,
max_retries=config.get("max_retries", 3),
retry_delay=config.get("retry_delay", 1.0),
)
if llm_type == "configurable":
return ConfigurableLLM(
model_name=config.get("model_name", "config-v1"),
response_style=config.get("style", "technical"),
max_length=config.get("max_length", 500),
include_confidence=config.get("include_confidence", False),
)
if llm_type == "expert":
return DomainExpertLLM(
domain=config.get("domain", "general"),
expertise_level=config.get("expertise_level", 0.8),
)
# Default fallback
return ConfigurableLLM()
def main():
logger.info("Advanced Custom LLM Integration Examples")
logger.info("=" * 60)
# Example 1: Using a retry wrapper
logger.info("\n1. Retry Wrapper Example:")
base_llm = ConfigurableLLM(response_style="technical")
retry_llm = RetryLLM(base_llm=base_llm, max_retries=3)
snapshot = create_settings_snapshot(
provider="retry_tech",
overrides={"search.tool": "wikipedia"},
)
result = quick_summary(
query="Explain quantum computing applications",
llms={"retry_tech": retry_llm},
settings_snapshot=snapshot,
)
logger.info(f"Summary: {result['summary'][:200]}...")
# Example 2: Multiple domain experts
logger.info("\n\n2. Multiple Domain Experts:")
experts = {
"medical_expert": DomainExpertLLM(
domain="medical", expertise_level=0.95
),
"tech_expert": DomainExpertLLM(domain="technical", expertise_level=0.9),
"finance_expert": DomainExpertLLM(
domain="finance", expertise_level=0.85
),
}
# Medical query
snapshot = create_settings_snapshot(
provider="medical_expert",
overrides={"search.tool": "pubmed"},
)
_ = quick_summary(
query="What are the latest treatments for diabetes?",
llms=experts,
settings_snapshot=snapshot,
)
logger.info(
"Medical summary retrieved successfully. Content not logged for privacy."
)
# Example 3: Factory with configuration
logger.info("\n\n3. Factory Configuration Example:")
# Configuration for a technical writer
tech_writer_config = {
"type": "configurable",
"model_name": "tech-writer-v2",
"style": "technical",
"max_length": 1000,
"include_confidence": True,
}
# Configuration for a retry wrapper around the technical writer
robust_config = {
"type": "retry",
"max_retries": 5,
"retry_delay": 0.5,
"base_config": tech_writer_config,
}
snapshot = create_settings_snapshot(
provider="robust_writer",
overrides={"search.tool": "arxiv"},
)
result = quick_summary(
query="How do neural networks learn?",
llms={
"robust_writer": lambda **kwargs: create_configured_llm(
robust_config
)
},
settings_snapshot=snapshot,
)
logger.info(f"Robust Writer: {result['summary'][:150]}...")
# Example 4: Research pipeline with different LLMs
logger.info("\n\n4. Multi-Stage Research Pipeline:")
# Stage 1: Quick exploration with simple LLM
simple_llm = ConfigurableLLM(response_style="simple", max_length=200)
snapshot = create_settings_snapshot(provider="simple")
initial = quick_summary(
query="Climate change impacts on agriculture",
llms={"simple": simple_llm},
settings_snapshot=snapshot,
iterations=1,
)
logger.info(f"Initial exploration: {initial['summary'][:100]}...")
# Stage 2: Detailed research with expert
expert_llm = DomainExpertLLM(domain="technical", expertise_level=0.95)
snapshot = create_settings_snapshot(provider="expert")
detailed = detailed_research(
query="Climate change impacts on agriculture: focus on technology solutions",
llms={"expert": expert_llm},
settings_snapshot=snapshot,
iterations=2,
)
logger.info(f"Expert analysis: {detailed['summary'][:150]}...")
# Example 5: Combining custom LLMs with custom retrievers
logger.info("\n\n5. Custom LLM + Retriever Combination:")
# Mock retriever for demonstration
class MockRetriever:
def get_relevant_documents(self, query):
return [
{"page_content": f"Mock document about {query}", "metadata": {}}
]
custom_llm = ConfigurableLLM(
model_name="integrated-v1",
response_style="technical",
include_confidence=True,
)
snapshot = create_settings_snapshot(
provider="integrated",
overrides={"search.tool": "company_docs"},
)
result = quick_summary(
query="Internal company policies on remote work",
llms={"integrated": custom_llm},
retrievers={"company_docs": MockRetriever()},
settings_snapshot=snapshot,
)
logger.info(f"Integrated result: {result['summary'][:150]}...")
if __name__ == "__main__":
main()
@@ -0,0 +1,129 @@
"""
Example of using custom LangChain LLMs with Local Deep Research.
This example shows how to integrate your own LLM implementations or wrappers
with LDR's research functions.
"""
from typing import Any, List, Optional
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import AIMessage, BaseMessage
from langchain_core.outputs import ChatGeneration, ChatResult
from local_deep_research.api import (
create_settings_snapshot,
detailed_research,
quick_summary,
)
class CustomLLM(BaseChatModel):
"""Example custom LLM implementation."""
model_name: str = "custom-model"
temperature: float = 0.7
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[Any] = None,
**kwargs: Any,
) -> ChatResult:
"""Generate a response. This is where you'd call your custom model."""
# This is a mock implementation - replace with your actual model call
response_text = f"This is a response from {self.model_name} to: {messages[-1].content}"
# Create the response
message = AIMessage(content=response_text)
generation = ChatGeneration(message=message)
return ChatResult(generations=[generation])
@property
def _llm_type(self) -> str:
"""Return identifier for this LLM."""
return "custom"
def custom_llm_factory(
model_name: str = "factory-model", temperature: float = 0.5, **kwargs
):
"""Factory function that creates a custom LLM instance."""
return CustomLLM(model_name=model_name, temperature=temperature)
def main():
# Example 1: Using a custom LLM instance
custom_llm = CustomLLM(model_name="my-custom-model", temperature=0.8)
snapshot = create_settings_snapshot(
provider="my_custom",
overrides={"search.tool": "wikipedia"},
)
result = quick_summary(
query="What are the latest advances in quantum computing?",
llms={"my_custom": custom_llm},
settings_snapshot=snapshot,
)
print("Summary with custom LLM instance:")
print(result["summary"])
print("-" * 80)
# Example 2: Using a factory function
snapshot = create_settings_snapshot(
provider="factory_llm",
temperature=0.3,
overrides={"search.tool": "wikipedia"},
)
result = quick_summary(
query="Explain the benefits of renewable energy",
llms={"factory_llm": custom_llm_factory},
model_name="renewable-expert", # This gets passed to the factory
settings_snapshot=snapshot,
)
print("\nSummary with factory-created LLM:")
print(result["summary"])
print("-" * 80)
# Example 3: Multiple custom LLMs
llms = {
"technical": CustomLLM(model_name="technical-writer", temperature=0.2),
"creative": CustomLLM(model_name="creative-writer", temperature=0.9),
}
# Technical analysis
snapshot = create_settings_snapshot(
provider="technical",
overrides={"search.tool": "arxiv"},
)
technical_result = detailed_research(
query="How do neural networks work?",
llms=llms,
settings_snapshot=snapshot,
)
print("\nTechnical analysis:")
print(technical_result["summary"])
print("-" * 80)
# Creative exploration
snapshot = create_settings_snapshot(
provider="creative",
overrides={"search.tool": "wikipedia"},
)
creative_result = quick_summary(
query="What are the philosophical implications of AI?",
llms=llms,
settings_snapshot=snapshot,
)
print("\nCreative exploration:")
print(creative_result["summary"])
if __name__ == "__main__":
main()
@@ -0,0 +1,348 @@
"""
Mock LLM example for testing Local Deep Research without API costs.
This example shows how to create mock LLMs that return predefined responses,
useful for:
- Testing research pipelines
- Development without API keys
- Debugging specific scenarios
- CI/CD pipelines
"""
import json
from typing import Any, Dict, List, Optional
from langchain_core.documents import Document
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import AIMessage, BaseMessage
from langchain_core.outputs import ChatGeneration, ChatResult
from langchain_core.retrievers import BaseRetriever
from local_deep_research.api import (
create_settings_snapshot,
generate_report,
quick_summary,
)
_DEFAULT_RESPONSES: Dict[str, str] = {
"default": "This is a mock response for testing purposes.",
"quantum": "Quantum computing uses quantum mechanics principles like superposition and entanglement to process information in fundamentally new ways.",
"climate": "Climate change refers to long-term shifts in global temperatures and weather patterns, primarily driven by human activities.",
"ai": "Artificial Intelligence encompasses machine learning, neural networks, and systems that can perform tasks requiring human intelligence.",
"summary": "Based on the search results, here is a comprehensive summary of the findings.",
"report": "# Research Report\n\n## Executive Summary\n\nThis report provides detailed analysis.\n\n## Findings\n\n1. Key finding one\n2. Key finding two",
}
class MockLLM(BaseChatModel):
"""Mock LLM that returns predefined responses based on queries."""
response_map: Optional[Dict[str, str]] = None
call_history: Optional[List[Dict]] = None
def model_post_init(self, __context: Any) -> None:
super().model_post_init(__context)
if self.response_map is None:
self.response_map = dict(_DEFAULT_RESPONSES)
if self.call_history is None:
self.call_history = []
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[Any] = None,
**kwargs: Any,
) -> ChatResult:
"""Generate mock response based on query content."""
# Extract query
query = messages[-1].content.lower() if messages else ""
# Log the call
self.call_history.append(
{
"messages": [
{"role": m.__class__.__name__, "content": m.content}
for m in messages
],
"kwargs": kwargs,
}
)
# Find matching response
response = self.response_map.get("default", "Mock response")
for key, value in self.response_map.items():
if key in query:
response = value
break
# Create response
message = AIMessage(content=response)
generation = ChatGeneration(message=message)
return ChatResult(generations=[generation])
@property
def _llm_type(self) -> str:
return "mock"
def get_call_history(self) -> List[Dict]:
"""Get history of all calls made to this LLM."""
return self.call_history
def clear_history(self):
"""Clear call history."""
self.call_history = []
class ScenarioMockLLM(BaseChatModel):
"""Mock LLM that simulates specific scenarios for testing."""
scenario: str = "success"
call_count: int = 0
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[Any] = None,
**kwargs: Any,
) -> ChatResult:
"""Generate response based on scenario."""
self.call_count += 1
if self.scenario == "success":
response = self._success_response(messages)
elif self.scenario == "partial_failure":
response = self._partial_failure_response()
elif self.scenario == "empty":
response = ""
elif self.scenario == "verbose":
response = self._verbose_response(messages)
elif self.scenario == "json":
response = self._json_response(messages)
else:
response = f"Unknown scenario: {self.scenario}"
message = AIMessage(content=response)
generation = ChatGeneration(message=message)
return ChatResult(generations=[generation])
def _success_response(self, messages):
"""Generate successful response."""
query = messages[-1].content if messages else "query"
return f"Successfully analyzed: {query}. Found 5 relevant sources with high confidence."
def _partial_failure_response(self):
"""Generate partial failure response."""
if self.call_count % 3 == 0:
return "Unable to process query due to insufficient data."
return "Partial results found. Limited information available."
def _verbose_response(self, messages):
"""Generate verbose response for testing truncation."""
query = messages[-1].content if messages else "query"
return f"""
Detailed Analysis of: {query}
Section 1: Introduction
{"-" * 50}
This is a comprehensive analysis with multiple sections.
Section 2: Methodology
{"-" * 50}
We used advanced techniques to analyze this query.
Section 3: Findings
{"-" * 50}
Finding 1: Important discovery about the topic.
Finding 2: Another significant insight.
Finding 3: Additional relevant information.
Section 4: Conclusion
{"-" * 50}
In conclusion, this analysis provides valuable insights.
""" + "\n".join([f"Additional point {i}" for i in range(20)])
def _json_response(self, messages):
"""Generate JSON response for testing parsing."""
query = messages[-1].content if messages else "query"
data = {
"query": query,
"findings": [
{"id": 1, "content": "First finding", "confidence": 0.9},
{"id": 2, "content": "Second finding", "confidence": 0.85},
],
"summary": "JSON-formatted response for testing",
"metadata": {"timestamp": "2024-01-01T00:00:00Z", "version": "1.0"},
}
return json.dumps(data, indent=2)
@property
def _llm_type(self) -> str:
return f"scenario_{self.scenario}"
class MockRetriever(BaseRetriever):
"""Offline retriever returning canned documents.
Registered as a search engine to keep the pipeline fully offline —
otherwise falling back to the default engines hits live services.
"""
def _get_relevant_documents(self, query, *, run_manager=None):
return [
Document(
page_content=f"Mock document 1 about {query}",
metadata={"source": "mock://doc1"},
),
Document(
page_content=f"Mock document 2 about {query}",
metadata={"source": "mock://doc2"},
),
]
def test_basic_mock():
"""Test basic mock functionality."""
print("Testing Basic Mock LLM")
print("-" * 40)
mock_llm = MockLLM()
snapshot = create_settings_snapshot(
provider="mock",
overrides={"search.tool": "mock_retriever"},
)
result = quick_summary(
query="Tell me about quantum computing",
llms={"mock": mock_llm},
retrievers={"mock_retriever": MockRetriever()},
settings_snapshot=snapshot,
iterations=1,
)
print(f"Result: {result['summary']}")
print(f"Call history: {len(mock_llm.get_call_history())} calls")
print()
def test_scenario_mocks():
"""Test different scenario mocks."""
print("Testing Scenario Mocks")
print("-" * 40)
scenarios = ["success", "partial_failure", "empty", "verbose", "json"]
for scenario in scenarios:
print(f"\nScenario: {scenario}")
mock_llm = ScenarioMockLLM(scenario=scenario)
try:
snapshot = create_settings_snapshot(
provider=f"mock_{scenario}",
overrides={"search.tool": "mock_retriever"},
)
result = quick_summary(
query="Test query for scenario",
llms={f"mock_{scenario}": mock_llm},
retrievers={"mock_retriever": MockRetriever()},
settings_snapshot=snapshot,
iterations=1,
)
print(f"Summary preview: {result['summary'][:100]}...")
print(f"Calls made: {mock_llm.call_count}")
except Exception as e:
print(f"Error in scenario {scenario}: {e}")
def test_mock_in_pipeline():
"""Test mock LLM in a full research pipeline."""
print("\nTesting Mock in Research Pipeline")
print("-" * 40)
# Create specialized mocks for different stages
response_map = {
"questions": "Generated questions: 1) What is X? 2) How does Y work? 3) What are the benefits?",
"analysis": "Analysis complete. Key findings: A, B, and C.",
"synthesis": "Synthesis: Combining all findings into coherent summary.",
"report": "# Final Report\n\n## Summary\n\nAll findings have been compiled.",
}
mock_llm = MockLLM(response_map=response_map)
# Test with report generation
snapshot = create_settings_snapshot(
provider="pipeline_mock",
overrides={"search.tool": "mock_retriever"},
)
report = generate_report(
query="Create a comprehensive report",
llms={"pipeline_mock": mock_llm},
retrievers={"mock_retriever": MockRetriever()},
settings_snapshot=snapshot,
searches_per_section=1,
)
print(f"Report generated: {len(report.get('content', ''))} characters")
print(f"Total LLM calls: {len(mock_llm.get_call_history())}")
# Analyze call patterns
print("\nCall Analysis:")
for i, call in enumerate(mock_llm.get_call_history()[:5]): # First 5 calls
last_message = (
call["messages"][-1]["content"]
if call["messages"]
else "No message"
)
print(f" Call {i + 1}: {last_message[:50]}...")
def test_mock_with_custom_retriever():
"""Test mock LLM with custom retriever."""
print("\nTesting Mock LLM with Custom Retriever")
print("-" * 40)
mock_llm = MockLLM(
response_map={
"default": "Analyzed documents and found relevant information.",
"summary": "Summary: Based on internal documents, the answer is clear.",
}
)
snapshot = create_settings_snapshot(
provider="mock",
overrides={"search.tool": "mock_retriever"},
)
result = quick_summary(
query="Internal policy question",
llms={"mock": mock_llm},
retrievers={"mock_retriever": MockRetriever()},
settings_snapshot=snapshot,
)
print(f"Result: {result['summary']}")
print(f"Sources: {result.get('sources', [])}")
def main():
"""Run all mock examples."""
test_basic_mock()
test_scenario_mocks()
test_mock_in_pipeline()
test_mock_with_custom_retriever()
print("\n" + "=" * 60)
print("Mock LLM Testing Complete!")
print(
"Use these patterns to test your research pipelines without API costs."
)
if __name__ == "__main__":
main()