""" Agentic RAG Service - State-of-the-art autonomous agent-based retrieval and reasoning. Implements multi-agent orchestration, advanced reasoning chains, and autonomous workflows. """ from __future__ import annotations import asyncio import logging from typing import Any, Dict, List, Optional, Tuple from dataclasses import dataclass from enum import Enum import json import uuid from datetime import datetime from app.services.vector_service import VectorService from app.services.llm_service import llm_service from app.core.cache import cache_service logger = logging.getLogger(__name__) class AgentType(Enum): """Types of specialized agents in the system.""" RESEARCH = "research" ANALYSIS = "analysis" SYNTHESIS = "synthesis" VALIDATION = "validation" PLANNING = "planning" EXECUTION = "execution" class ReasoningType(Enum): """Types of reasoning approaches.""" CHAIN_OF_THOUGHT = "chain_of_thought" TREE_OF_THOUGHTS = "tree_of_thoughts" MULTI_STEP = "multi_step" PARALLEL = "parallel" @dataclass class AgentTask: """Represents a task assigned to an agent.""" id: str agent_type: AgentType description: str input_data: Dict[str, Any] dependencies: List[str] priority: int created_at: datetime status: str = "pending" result: Optional[Dict[str, Any]] = None error: Optional[str] = None @dataclass class ReasoningStep: """Represents a step in a reasoning chain.""" id: str step_type: str description: str input: Dict[str, Any] output: Optional[Dict[str, Any]] = None confidence: float = 0.0 validation_status: str = "pending" class Agent: """Base class for all agents in the system.""" def __init__(self, agent_type: AgentType, agent_id: str): self.agent_type = agent_type self.agent_id = agent_id self.memory = {} self.learning_history = [] async def execute(self, task: AgentTask) -> Dict[str, Any]: """Execute a task and return results.""" raise NotImplementedError async def learn(self, feedback: Dict[str, Any]) -> None: """Learn from feedback to improve future performance.""" self.learning_history.append({ "timestamp": datetime.utcnow().isoformat(), "feedback": feedback }) def get_memory(self) -> Dict[str, Any]: """Get agent's current memory state.""" return self.memory.copy() def update_memory(self, key: str, value: Any) -> None: """Update agent's memory.""" self.memory[key] = value class ResearchAgent(Agent): """Specialized agent for information retrieval and research.""" def __init__(self, vector_service: VectorService): super().__init__(AgentType.RESEARCH, f"research_{uuid.uuid4().hex[:8]}") self.vector_service = vector_service async def execute(self, task: AgentTask) -> Dict[str, Any]: """Execute research task with autonomous retrieval decisions.""" try: query = task.input_data.get("query", "") context = task.input_data.get("context", {}) # Autonomous decision making for retrieval strategy retrieval_strategy = await self._determine_retrieval_strategy(query, context) # Execute retrieval based on strategy if retrieval_strategy == "semantic": results = await self._semantic_retrieval(query, context) elif retrieval_strategy == "hybrid": results = await self._hybrid_retrieval(query, context) elif retrieval_strategy == "structured": results = await self._structured_retrieval(query, context) else: results = await self._multi_modal_retrieval(query, context) # Autonomous filtering and ranking filtered_results = await self._autonomous_filtering(results, query) # Update memory with retrieval patterns self.update_memory("last_retrieval_strategy", retrieval_strategy) self.update_memory("retrieval_patterns", self.memory.get("retrieval_patterns", []) + [{ "query": query, "strategy": retrieval_strategy, "results_count": len(filtered_results) }]) return { "status": "success", "results": filtered_results, "strategy_used": retrieval_strategy, "confidence": self._calculate_confidence(filtered_results), "metadata": { "agent_id": self.agent_id, "execution_time": datetime.utcnow().isoformat() } } except Exception as e: logger.error(f"Research agent execution failed: {e}") return { "status": "error", "error": str(e), "metadata": { "agent_id": self.agent_id, "execution_time": datetime.utcnow().isoformat() } } async def _determine_retrieval_strategy(self, query: str, context: Dict[str, Any]) -> str: """Autonomously determine the best retrieval strategy.""" # Analyze query characteristics query_analysis = await self._analyze_query(query) # Consider context and history historical_patterns = self.memory.get("retrieval_patterns", []) # Make autonomous decision if query_analysis.get("has_structured_terms", False): return "structured" elif query_analysis.get("complexity", "low") == "high": return "hybrid" elif query_analysis.get("modality", "text") == "multi_modal": return "multi_modal" else: return "semantic" async def _analyze_query(self, query: str) -> Dict[str, Any]: """Analyze query characteristics for strategy selection.""" analysis_prompt = f""" Analyze the following query and determine its characteristics: Query: {query} Return a JSON object with: - complexity: "low", "medium", "high" - modality: "text", "structured", "multi_modal" - has_structured_terms: boolean - requires_context: boolean - estimated_retrieval_count: number """ try: response = await llm_service.generate_text( analysis_prompt, tenant_id=task.input_data.get("tenant_id", "default"), task="classification", temperature=0.1 ) return json.loads(response.get("text", "{}")) except Exception: # Fallback analysis return { "complexity": "medium", "modality": "text", "has_structured_terms": False, "requires_context": True, "estimated_retrieval_count": 10 } async def _semantic_retrieval(self, query: str, context: Dict[str, Any]) -> List[Dict[str, Any]]: """Perform semantic retrieval.""" tenant_id = context.get("tenant_id", "default") return await self.vector_service.search_similar( tenant_id=tenant_id, query=query, limit=15 ) async def _hybrid_retrieval(self, query: str, context: Dict[str, Any]) -> List[Dict[str, Any]]: """Perform hybrid retrieval combining semantic and keyword search.""" tenant_id = context.get("tenant_id", "default") return await self.vector_service.hybrid_search( tenant_id=tenant_id, query=query, limit=15 ) async def _structured_retrieval(self, query: str, context: Dict[str, Any]) -> List[Dict[str, Any]]: """Perform structured data retrieval.""" tenant_id = context.get("tenant_id", "default") return await self.vector_service.search_structured_data( tenant_id=tenant_id, query=query, data_type="table", limit=15 ) async def _multi_modal_retrieval(self, query: str, context: Dict[str, Any]) -> List[Dict[str, Any]]: """Perform multi-modal retrieval across different content types.""" tenant_id = context.get("tenant_id", "default") # Retrieve from different modalities text_results = await self.vector_service.search_similar( tenant_id=tenant_id, query=query, limit=8, chunk_types=["text"] ) table_results = await self.vector_service.search_structured_data( tenant_id=tenant_id, query=query, data_type="table", limit=4 ) chart_results = await self.vector_service.search_structured_data( tenant_id=tenant_id, query=query, data_type="chart", limit=3 ) # Combine and rank results all_results = text_results + table_results + chart_results return sorted(all_results, key=lambda x: x.get("score", 0), reverse=True) async def _autonomous_filtering(self, results: List[Dict[str, Any]], query: str) -> List[Dict[str, Any]]: """Autonomously filter and rank results.""" if not results: return [] # Use LLM to evaluate relevance evaluation_prompt = f""" Evaluate the relevance of each result to the query: Query: {query} Results: {json.dumps(results[:10], indent=2)} For each result, provide a relevance score (0-1) and brief reasoning. Return as JSON array with format: [{{"id": "result_id", "relevance_score": 0.8, "reasoning": "..."}}] """ try: response = await llm_service.generate_text( evaluation_prompt, tenant_id="default", task="analysis", temperature=0.1 ) evaluations = json.loads(response.get("text", "[]")) # Apply evaluations to results for result in results: for eval_item in evaluations: if eval_item.get("id") == result.get("id"): result["llm_relevance_score"] = eval_item.get("relevance_score", 0.5) result["llm_reasoning"] = eval_item.get("reasoning", "") break # Filter by relevance threshold and re-rank filtered_results = [ r for r in results if r.get("llm_relevance_score", 0.5) > 0.3 ] return sorted(filtered_results, key=lambda x: x.get("llm_relevance_score", 0), reverse=True) except Exception as e: logger.warning(f"Autonomous filtering failed, using original results: {e}") return results def _calculate_confidence(self, results: List[Dict[str, Any]]) -> float: """Calculate confidence in retrieval results.""" if not results: return 0.0 # Consider multiple factors avg_score = sum(r.get("score", 0) for r in results) / len(results) avg_llm_score = sum(r.get("llm_relevance_score", 0.5) for r in results) / len(results) result_count = len(results) # Weighted confidence calculation confidence = (avg_score * 0.4 + avg_llm_score * 0.4 + min(result_count / 10, 1.0) * 0.2) return min(confidence, 1.0) class AnalysisAgent(Agent): """Specialized agent for analysis and reasoning.""" def __init__(self): super().__init__(AgentType.ANALYSIS, f"analysis_{uuid.uuid4().hex[:8]}") async def execute(self, task: AgentTask) -> Dict[str, Any]: """Execute analysis task with advanced reasoning.""" try: query = task.input_data.get("query", "") retrieved_data = task.input_data.get("retrieved_data", []) reasoning_type = task.input_data.get("reasoning_type", ReasoningType.CHAIN_OF_THOUGHT) # Choose reasoning approach if reasoning_type == ReasoningType.TREE_OF_THOUGHTS: analysis_result = await self._tree_of_thoughts_analysis(query, retrieved_data) elif reasoning_type == ReasoningType.MULTI_STEP: analysis_result = await self._multi_step_analysis(query, retrieved_data) else: analysis_result = await self._chain_of_thought_analysis(query, retrieved_data) # Validate analysis validation_result = await self._validate_analysis(analysis_result, query, retrieved_data) return { "status": "success", "analysis": analysis_result, "validation": validation_result, "reasoning_type": reasoning_type.value, "confidence": validation_result.get("confidence", 0.0), "metadata": { "agent_id": self.agent_id, "execution_time": datetime.utcnow().isoformat() } } except Exception as e: logger.error(f"Analysis agent execution failed: {e}") return { "status": "error", "error": str(e), "metadata": { "agent_id": self.agent_id, "execution_time": datetime.utcnow().isoformat() } } async def _chain_of_thought_analysis(self, query: str, data: List[Dict[str, Any]]) -> Dict[str, Any]: """Perform chain of thought analysis.""" context = self._format_context(data) analysis_prompt = f""" Analyze the following information step by step to answer the query: Query: {query} Context: {context} Please provide your analysis in the following format: 1. Key Findings: [List main findings] 2. Reasoning: [Step-by-step reasoning] 3. Conclusions: [Final conclusions] 4. Confidence: [0-1 score] 5. Limitations: [Any limitations or uncertainties] """ response = await llm_service.generate_text( analysis_prompt, tenant_id="default", task="analysis", temperature=0.3 ) return { "method": "chain_of_thought", "analysis": response.get("text", ""), "steps": self._extract_reasoning_steps(response.get("text", "")) } async def _tree_of_thoughts_analysis(self, query: str, data: List[Dict[str, Any]]) -> Dict[str, Any]: """Perform tree of thoughts analysis with multiple reasoning paths.""" context = self._format_context(data) # Generate multiple reasoning paths paths_prompt = f""" For the query: "{query}" Context: {context} Generate 3 different reasoning approaches to analyze this information. Each approach should be distinct and explore different aspects. Return as JSON: {{ "paths": [ {{ "approach": "description", "focus": "what this path focuses on", "reasoning": "step-by-step reasoning" }} ] }} """ paths_response = await llm_service.generate_text( paths_prompt, tenant_id="default", task="analysis", temperature=0.7 ) try: paths_data = json.loads(paths_response.get("text", "{}")) paths = paths_data.get("paths", []) # Evaluate each path evaluated_paths = [] for path in paths: evaluation = await self._evaluate_reasoning_path(path, query, context) evaluated_paths.append({ **path, "evaluation": evaluation }) # Synthesize best insights from all paths synthesis = await self._synthesize_paths(evaluated_paths, query) return { "method": "tree_of_thoughts", "paths": evaluated_paths, "synthesis": synthesis, "best_path": max(evaluated_paths, key=lambda x: x["evaluation"].get("score", 0)) } except Exception as e: logger.warning(f"Tree of thoughts failed, falling back to CoT: {e}") return await self._chain_of_thought_analysis(query, data) async def _multi_step_analysis(self, query: str, data: List[Dict[str, Any]]) -> Dict[str, Any]: """Perform multi-step analysis with validation at each step.""" context = self._format_context(data) steps = [ ("extract_key_information", "Extract key information from the context"), ("identify_patterns", "Identify patterns and relationships"), ("analyze_implications", "Analyze implications and consequences"), ("evaluate_evidence", "Evaluate the strength of evidence"), ("form_conclusions", "Form conclusions and recommendations") ] analysis_steps = [] current_context = context for step_id, step_description in steps: step_prompt = f""" Step: {step_description} Query: {query} Current Context: {current_context} Previous Steps: {json.dumps(analysis_steps, indent=2)} Perform this step and provide: 1. Analysis: [Your analysis for this step] 2. Updated Context: [Any new information or insights] 3. Confidence: [0-1 score for this step] """ step_response = await llm_service.generate_text( step_prompt, tenant_id="default", task="analysis", temperature=0.3 ) step_result = { "step_id": step_id, "description": step_description, "analysis": step_response.get("text", ""), "confidence": 0.7 # Default confidence } analysis_steps.append(step_result) # Update context for next step current_context += f"\n\nStep {step_id} Analysis: {step_response.get('text', '')}" # Final synthesis synthesis = await self._synthesize_multi_step(analysis_steps, query) return { "method": "multi_step", "steps": analysis_steps, "synthesis": synthesis, "overall_confidence": sum(s.get("confidence", 0) for s in analysis_steps) / len(analysis_steps) } async def _evaluate_reasoning_path(self, path: Dict[str, Any], query: str, context: str) -> Dict[str, Any]: """Evaluate the quality of a reasoning path.""" evaluation_prompt = f""" Evaluate this reasoning approach: Query: {query} Approach: {path.get('approach', '')} Reasoning: {path.get('reasoning', '')} Rate on a scale of 0-1: - Logical coherence - Relevance to query - Completeness - Novelty of insights Return as JSON: {{"score": 0.8, "coherence": 0.9, "relevance": 0.8, "completeness": 0.7, "novelty": 0.6}} """ response = await llm_service.generate_text( evaluation_prompt, tenant_id="default", task="analysis", temperature=0.1 ) try: return json.loads(response.get("text", "{}")) except Exception: return {"score": 0.5, "coherence": 0.5, "relevance": 0.5, "completeness": 0.5, "novelty": 0.5} async def _synthesize_paths(self, paths: List[Dict[str, Any]], query: str) -> Dict[str, Any]: """Synthesize insights from multiple reasoning paths.""" synthesis_prompt = f""" Synthesize insights from multiple reasoning approaches: Query: {query} Approaches: {json.dumps(paths, indent=2)} Provide a comprehensive synthesis that combines the best insights from all approaches. """ response = await llm_service.generate_text( synthesis_prompt, tenant_id="default", task="synthesis", temperature=0.3 ) return { "synthesis": response.get("text", ""), "contributing_paths": [p["approach"] for p in paths if p["evaluation"].get("score", 0) > 0.6] } async def _synthesize_multi_step(self, steps: List[Dict[str, Any]], query: str) -> Dict[str, Any]: """Synthesize results from multi-step analysis.""" synthesis_prompt = f""" Synthesize the results from multi-step analysis: Query: {query} Steps: {json.dumps(steps, indent=2)} Provide a comprehensive synthesis of all steps. """ response = await llm_service.generate_text( synthesis_prompt, tenant_id="default", task="synthesis", temperature=0.3 ) return { "synthesis": response.get("text", ""), "key_insights": [s["analysis"] for s in steps if s.get("confidence", 0) > 0.7] } async def _validate_analysis(self, analysis: Dict[str, Any], query: str, data: List[Dict[str, Any]]) -> Dict[str, Any]: """Validate the analysis results.""" validation_prompt = f""" Validate this analysis: Query: {query} Analysis: {json.dumps(analysis, indent=2)} Check for: 1. Logical consistency 2. Evidence support 3. Completeness 4. Relevance to query Return validation results as JSON. """ response = await llm_service.generate_text( validation_prompt, tenant_id="default", task="validation", temperature=0.1 ) try: return json.loads(response.get("text", "{}")) except Exception: return {"confidence": 0.7, "issues": [], "validation_status": "partial"} def _format_context(self, data: List[Dict[str, Any]]) -> str: """Format retrieved data as context.""" context_lines = [] for item in data[:10]: # Limit to top 10 results meta = f"doc={item.get('document_id','?')} pages={item.get('page_numbers',[])} type={item.get('chunk_type','?')}" text = item.get("text", "").strip() if text: context_lines.append(f"[{meta}] {text}") return "\n\n".join(context_lines) def _extract_reasoning_steps(self, analysis: str) -> List[str]: """Extract reasoning steps from analysis text.""" # Simple extraction - in production, use more sophisticated parsing lines = analysis.split('\n') steps = [] for line in lines: if line.strip().startswith(('1.', '2.', '3.', '4.', '5.')): steps.append(line.strip()) return steps class SynthesisAgent(Agent): """Specialized agent for synthesizing and generating final responses.""" def __init__(self): super().__init__(AgentType.SYNTHESIS, f"synthesis_{uuid.uuid4().hex[:8]}") async def execute(self, task: AgentTask) -> Dict[str, Any]: """Execute synthesis task to generate final response.""" try: query = task.input_data.get("query", "") research_results = task.input_data.get("research_results", {}) analysis_results = task.input_data.get("analysis_results", {}) context = task.input_data.get("context", {}) # Synthesize all information synthesis = await self._synthesize_information( query, research_results, analysis_results, context ) # Generate final response final_response = await self._generate_response(query, synthesis, context) # Add citations and metadata response_with_metadata = await self._add_metadata(final_response, research_results, analysis_results) return { "status": "success", "response": response_with_metadata, "synthesis": synthesis, "confidence": synthesis.get("confidence", 0.0), "metadata": { "agent_id": self.agent_id, "execution_time": datetime.utcnow().isoformat() } } except Exception as e: logger.error(f"Synthesis agent execution failed: {e}") return { "status": "error", "error": str(e), "metadata": { "agent_id": self.agent_id, "execution_time": datetime.utcnow().isoformat() } } async def _synthesize_information( self, query: str, research_results: Dict[str, Any], analysis_results: Dict[str, Any], context: Dict[str, Any] ) -> Dict[str, Any]: """Synthesize information from research and analysis.""" synthesis_prompt = f""" Synthesize the following information into a comprehensive response: Query: {query} Research Results: {json.dumps(research_results, indent=2)} Analysis Results: {json.dumps(analysis_results, indent=2)} Context: {json.dumps(context, indent=2)} Create a synthesis that: 1. Addresses the query directly 2. Incorporates key insights from research 3. Uses analysis to provide reasoning 4. Maintains accuracy and relevance 5. Provides actionable insights where applicable """ response = await llm_service.generate_text( synthesis_prompt, tenant_id=context.get("tenant_id", "default"), task="synthesis", temperature=0.3 ) return { "synthesis": response.get("text", ""), "confidence": self._calculate_synthesis_confidence(research_results, analysis_results), "key_insights": self._extract_key_insights(response.get("text", "")) } async def _generate_response(self, query: str, synthesis: Dict[str, Any], context: Dict[str, Any]) -> str: """Generate the final response.""" response_prompt = f""" Generate a final response to the query based on the synthesis: Query: {query} Synthesis: {synthesis.get('synthesis', '')} Requirements: 1. Be direct and concise 2. Use clear, professional language 3. Include relevant citations 4. Provide actionable insights where applicable 5. Acknowledge any limitations or uncertainties """ response = await llm_service.generate_text( response_prompt, tenant_id=context.get("tenant_id", "default"), task="synthesis", temperature=0.2 ) return response.get("text", "") async def _add_metadata( self, response: str, research_results: Dict[str, Any], analysis_results: Dict[str, Any] ) -> Dict[str, Any]: """Add metadata and citations to the response.""" # Extract citations from research results citations = [] if research_results.get("results"): for result in research_results["results"][:5]: # Top 5 citations citations.append({ "document_id": result.get("document_id"), "page_numbers": result.get("page_numbers", []), "chunk_type": result.get("chunk_type"), "score": result.get("score", 0) }) return { "text": response, "citations": citations, "research_confidence": research_results.get("confidence", 0.0), "analysis_confidence": analysis_results.get("confidence", 0.0), "overall_confidence": (research_results.get("confidence", 0.0) + analysis_results.get("confidence", 0.0)) / 2 } def _calculate_synthesis_confidence(self, research_results: Dict[str, Any], analysis_results: Dict[str, Any]) -> float: """Calculate confidence in synthesis.""" research_conf = research_results.get("confidence", 0.5) analysis_conf = analysis_results.get("confidence", 0.5) # Weighted average return (research_conf * 0.4 + analysis_conf * 0.6) def _extract_key_insights(self, synthesis: str) -> List[str]: """Extract key insights from synthesis.""" # Simple extraction - in production, use more sophisticated parsing lines = synthesis.split('\n') insights = [] for line in lines: if any(keyword in line.lower() for keyword in ['key', 'important', 'critical', 'significant']): insights.append(line.strip()) return insights[:5] # Limit to top 5 insights class AgenticRAGService: """Main service orchestrating agentic RAG operations.""" def __init__(self, vector_service: Optional[VectorService] = None): self.vector_service = vector_service or VectorService() self.agents = {} self.workflow_engine = None self._initialize_agents() def _initialize_agents(self): """Initialize all agents.""" self.agents[AgentType.RESEARCH] = ResearchAgent(self.vector_service) self.agents[AgentType.ANALYSIS] = AnalysisAgent() self.agents[AgentType.SYNTHESIS] = SynthesisAgent() async def answer( self, *, tenant_id: str, query: str, max_tokens: Optional[int] = None, temperature: Optional[float] = None, reasoning_type: ReasoningType = ReasoningType.CHAIN_OF_THOUGHT, enable_autonomous_workflow: bool = True ) -> Dict[str, Any]: """Generate answer using agentic RAG approach.""" # Check cache first cache_key = f"agentic_rag:answer:{tenant_id}:{hash(query)}" cached = await cache_service.get(cache_key, tenant_id) if isinstance(cached, dict) and cached.get("text"): return cached try: if enable_autonomous_workflow: result = await self._autonomous_workflow(tenant_id, query, reasoning_type) else: result = await self._simple_workflow(tenant_id, query, reasoning_type) # Cache result await cache_service.set(cache_key, result, tenant_id, expire=300) return result except Exception as e: logger.error(f"Agentic RAG failed: {e}") # Fallback to simple RAG return await self._fallback_rag(tenant_id, query) async def _autonomous_workflow( self, tenant_id: str, query: str, reasoning_type: ReasoningType ) -> Dict[str, Any]: """Execute autonomous workflow with multiple agents.""" # Create workflow context context = { "tenant_id": tenant_id, "query": query, "reasoning_type": reasoning_type, "workflow_id": str(uuid.uuid4()), "start_time": datetime.utcnow().isoformat() } # Phase 1: Research research_task = AgentTask( id=str(uuid.uuid4()), agent_type=AgentType.RESEARCH, description=f"Research information for query: {query}", input_data={"query": query, "context": context}, dependencies=[], priority=1, created_at=datetime.utcnow() ) research_results = await self.agents[AgentType.RESEARCH].execute(research_task) # Phase 2: Analysis analysis_task = AgentTask( id=str(uuid.uuid4()), agent_type=AgentType.ANALYSIS, description=f"Analyze research results for query: {query}", input_data={ "query": query, "retrieved_data": research_results.get("results", []), "reasoning_type": reasoning_type, "context": context }, dependencies=[research_task.id], priority=2, created_at=datetime.utcnow() ) analysis_results = await self.agents[AgentType.ANALYSIS].execute(analysis_task) # Phase 3: Synthesis synthesis_task = AgentTask( id=str(uuid.uuid4()), agent_type=AgentType.SYNTHESIS, description=f"Synthesize final response for query: {query}", input_data={ "query": query, "research_results": research_results, "analysis_results": analysis_results, "context": context }, dependencies=[research_task.id, analysis_task.id], priority=3, created_at=datetime.utcnow() ) synthesis_results = await self.agents[AgentType.SYNTHESIS].execute(synthesis_task) # Compile final result final_result = { "text": synthesis_results.get("response", {}).get("text", ""), "citations": synthesis_results.get("response", {}).get("citations", []), "model": "agentic_rag", "workflow_metadata": { "workflow_id": context["workflow_id"], "research_confidence": research_results.get("confidence", 0.0), "analysis_confidence": analysis_results.get("confidence", 0.0), "synthesis_confidence": synthesis_results.get("confidence", 0.0), "reasoning_type": reasoning_type.value, "execution_time": datetime.utcnow().isoformat() }, "agent_insights": { "research_strategy": research_results.get("strategy_used"), "analysis_method": analysis_results.get("reasoning_type"), "key_insights": synthesis_results.get("synthesis", {}).get("key_insights", []) } } return final_result async def _simple_workflow( self, tenant_id: str, query: str, reasoning_type: ReasoningType ) -> Dict[str, Any]: """Execute simplified workflow for basic queries.""" # Simple research research_results = await self.agents[AgentType.RESEARCH].execute( AgentTask( id=str(uuid.uuid4()), agent_type=AgentType.RESEARCH, description=f"Simple research for: {query}", input_data={"query": query, "context": {"tenant_id": tenant_id}}, dependencies=[], priority=1, created_at=datetime.utcnow() ) ) # Simple synthesis synthesis_results = await self.agents[AgentType.SYNTHESIS].execute( AgentTask( id=str(uuid.uuid4()), agent_type=AgentType.SYNTHESIS, description=f"Simple synthesis for: {query}", input_data={ "query": query, "research_results": research_results, "analysis_results": {}, "context": {"tenant_id": tenant_id} }, dependencies=[], priority=2, created_at=datetime.utcnow() ) ) return { "text": synthesis_results.get("response", {}).get("text", ""), "citations": synthesis_results.get("response", {}).get("citations", []), "model": "agentic_rag_simple", "confidence": synthesis_results.get("confidence", 0.0) } async def _fallback_rag(self, tenant_id: str, query: str) -> Dict[str, Any]: """Fallback to simple RAG if agentic approach fails.""" from app.services.rag_service import rag_service return await rag_service.answer( tenant_id=tenant_id, query=query ) async def get_agent_status(self) -> Dict[str, Any]: """Get status of all agents.""" status = {} for agent_type, agent in self.agents.items(): status[agent_type.value] = { "agent_id": agent.agent_id, "memory_size": len(agent.memory), "learning_history_size": len(agent.learning_history), "status": "active" } return status async def reset_agent_memory(self, agent_type: Optional[AgentType] = None) -> bool: """Reset agent memory.""" try: if agent_type: if agent_type in self.agents: self.agents[agent_type].memory = {} self.agents[agent_type].learning_history = [] else: for agent in self.agents.values(): agent.memory = {} agent.learning_history = [] return True except Exception as e: logger.error(f"Failed to reset agent memory: {e}") return False # Global agentic RAG service instance agentic_rag_service = AgenticRAGService()