""" 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()