import re from openai import OpenAI from typing import List, Dict, Any, Optional, Tuple import sys import os import traceback # Add the project root to the path to ensure imports work sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) # Import configuration from src.utils.config import CHAT_MODEL, OPENAI_API_KEY # Import other modules needed for the agents from src.models.retriever import Retriever class QueryAnalyzer: """ Agent responsible for analyzing and refining the user's query. """ def __init__(self, model: str = CHAT_MODEL): """Initialize the query analyzer.""" self.model = model self.client = OpenAI(api_key=OPENAI_API_KEY) def analyze_query(self, query: str) -> Dict[str, Any]: """ Analyze the user's query to extract key information and refine it if needed. Args: query: The user's query Returns: Dictionary containing analysis results """ # Create a system prompt for the query analyzer oriented toward Cathy Barriga defense system_prompt = ( "Eres un analista legal especializado en defensa penal relacionado con el delito económico. Tu tarea es analizar las consultas relacionadas con " "el caso de Cathy Barriga para identificar:" "\n1. Las posibles teorías de defensa aplicables a Cathy Barriga" "\n2. Los elementos del tipo penal que podrían ser refutados o desacreditados" "\n3. Los vacíos probatorios o debilidades en la acusación" "\n4. Las interpretaciones alternativas de los hechos que favorezcan a la defensa" "\n5. Los principios jurídicos que podrían invocarse a favor de la defensa (in dubio pro reo, presunción de inocencia, etc.)" "\n\nProporciona tu análisis en un formato estructurado que oriente la búsqueda hacia elementos exculpatorios o mitigantes." ) # Get analysis from the LLM try: response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": query} ], temperature=0.3 ) analysis = response.choices[0].message.content.strip() # Create a structured analysis struct_analysis = self._extract_structured_analysis(analysis, query) return { "original_query": query, "analysis": analysis, "structured_analysis": struct_analysis } except Exception as e: print(f"Error analyzing query: {e}") return { "original_query": query, "analysis": f"Error: {str(e)}", "structured_analysis": "" } def _extract_structured_analysis(self, analysis: str, query: str) -> str: """Extract a structured analysis from the raw analysis text.""" # This would normally do more sophisticated extraction # For demo purposes, we'll just format it with some headers formatted = "## Análisis fundamental del contexto\n\n" formatted += "- **Dominio y expertiz**: Defensa penalista con especialidad en delito económico\n" formatted += "- **Consulta Original**: {query}\n" # formatted += "- **Enfoque de Defensa**: Estrategia de defensa para Cathy Barriga\n" # formatted += "- **Conceptos Clave**: Presunción de inocencia, carga de la prueba, elementos del tipo penal\n" return formatted class ContextAggregator: """ Agent responsible for aggregating and organizing retrieved document chunks. """ def __init__(self, model: str = CHAT_MODEL): """Initialize the context aggregator.""" self.model = model self.client = OpenAI(api_key=OPENAI_API_KEY) def aggregate_context(self, query: str, retrieved_chunks: List[Dict[str, Any]]) -> str: """ Aggregate retrieved chunks into a coherent context. Args: query: The user's query retrieved_chunks: List of retrieved document chunks Returns: String containing the organized context """ # If small number of chunks, use a simpler approach if len(retrieved_chunks) <= 10: # For small number of chunks, just organize them chunk_contents = [ { 'source': chunk.get('source', 'unknown'), 'content': chunk.get('text', chunk.get('chunk', "No content available")), 'is_summary': False } for chunk in retrieved_chunks ] return self._organize_content(query, chunk_contents) else: # Group chunks by source sources = {} for chunk in retrieved_chunks: source = chunk.get('source', 'unknown') if source not in sources: sources[source] = [] sources[source].append(chunk) # Create summaries for each source summaries = [] for source, chunks in sources.items(): summary = self._summarize_chunks(source, chunks, query) summaries.append(summary) # Aggregate the summaries and individual chunks aggregated_context = self._organize_content(query, summaries) return aggregated_context def _summarize_chunks(self, source: str, chunks: List[Dict[str, Any]], query: str) -> Dict[str, Any]: """Summarize a group of chunks from the same source.""" # Combine chunks into a single text, handling different chunk formats try: chunks_text = "\n\n".join([chunk.get('text', chunk.get('chunk', "No content available")) for chunk in chunks]) except Exception as e: print(f"Error combining chunks: {e}") # Fallback to a safer method chunks_text = "" for chunk in chunks: try: if isinstance(chunk, dict): chunk_content = chunk.get('text', chunk.get('chunk', "No content available")) chunks_text += chunk_content + "\n\n" else: chunks_text += str(chunk) + "\n\n" except Exception as chunk_e: print(f"Error processing individual chunk: {chunk_e}") continue # Create a prompt for summarization oriented toward defense system_prompt = ( "Eres un experto en derecho penal especializado en organizar información para la defensa legal. Tu tarea es resumir, con un approach divide & conquer, " "los documentos proporcionados priorizando información que pueda ser útil para la defensa de Cathy Barriga. Enfócate en:" "\n1. Elementos exculpatorios o que cuestionen la culpabilidad" "\n2. Inconsistencias o debilidades en la evidencia de la acusación" "\n3. Interpretaciones alternativas de los hechos que favorezcan a la defendida" "\n4. Precedentes legales que podrían apoyar la defensa" "\n5. Circunstancias atenuantes o justificativas" "\n6. Vicios procedimentales que puedan ser alegados" "\nMantén la precisión fáctica pero organiza la información para construir la mejor argumentación posible." ) # Get summary from the LLM try: response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Consulta relacionada: {query}\n\nDocumentos de {source}:\n\n{chunks_text}"} ], temperature=0.3 ) summary = response.choices[0].message.content.strip() return { 'source': source, 'content': summary, 'is_summary': True, 'num_chunks': len(chunks) } except Exception as e: print(f"Error summarizing chunks from {source}: {e}") return { 'source': source, 'content': f"Error summarizing content: {str(e)}", 'is_summary': True, 'num_chunks': len(chunks) } def _organize_content(self, query: str, contents: List[Dict[str, Any]]) -> str: """Organize content items into a coherent structure.""" # Simple organization - separate summaries and regular chunks organized_text = f"# Relevant Legal Context for: {query}\n\n" # Add summaries first summaries = [item for item in contents if item.get('is_summary', False)] if summaries: organized_text += "## Summaries of Key Sources\n\n" for summary in summaries: organized_text += f"### {summary['source']}\n" organized_text += f"{summary['content']}\n\n" # Add individual chunks individual_chunks = [item for item in contents if not item.get('is_summary', False)] if individual_chunks: organized_text += "## Detalles Relevantes adicionales\n\n" for chunk in individual_chunks: organized_text += f"### De {chunk['source']}\n" organized_text += f"{chunk['content']}\n\n" return organized_text class AnswerGenerator: """ Agent responsible for generating comprehensive answers based on the context. """ def __init__(self, model: str = CHAT_MODEL): """Initialize the answer generator.""" self.model = model self.client = OpenAI(api_key=OPENAI_API_KEY) def generate_answer(self, query: str, context: str) -> str: """ Generate a comprehensive answer to the user's query using the provided context. Args: query: The user's query context: The organized context Returns: The generated answer """ # Create a system prompt for the answer generator focused on defense system_prompt = ( "Eres un abogado defensor experto especializado en derecho penal, particularmente en la defensa de casos complejos. " "Tu objetivo es estructurar con máxima precisión y claridad lo expuesto en el contexto. Al responder preguntas:\n" "\n1. Basa tus respuestas exclusivamente en la información proporcionada en el contexto y en principios jurídicos de defensa penal\n" "\n2. Cuestiona sistemáticamente las pruebas de cargo, identificando sus debilidades metodológicas, procedimentales o interpretativas\n" "\n3. Explora interpretaciones alternativas de los hechos que favorezcan a la defendida\n" "\n4. Identifica vicios procesales que puedan ser alegados\n" "\n5. Construye narrativas coherentes que expliquen los hechos de manera objetiva\n" "\n6. Cita fuentes específicas cuando te refieres a información o argumentos clave\n" "\n7. Estructura tu respuesta claramente con secciones apropiadas\n" "\n8. Maximiza la claridad y precisión de tus respuestas en todo momento, con un enfoque lógico, penalista, algorítmico y objetivo" # "\n8. Mantén un tono objetivo y profesional pero claramente orientado a la defensa, evitando cualquier admisión de culpabilidad" ) # Get answer from the LLM try: response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Consulta sobre la defensa de Cathy Barriga: {query}\n\nContexto:\n\n{context}"} ], temperature=0.3 ) answer = response.choices[0].message.content.strip() return answer except Exception as e: print(f"Error generating answer: {e}") return f"Error generating answer: {str(e)}" class AgentDirector: """ Director that coordinates the various specialized agents to process a query. """ def __init__(self, model: str = None, top_k: int = 200, debug: bool = False): """ Initialize the agent director. Args: model: The OpenAI chat model to use top_k: Number of chunks to retrieve debug: Whether to show detailed reasoning steps """ # Ensure model is not None, default to CHAT_MODEL if not provided self.model = model if model is not None else CHAT_MODEL self.top_k = top_k self.debug = debug self.retriever = Retriever(top_k=top_k) self.query_analyzer = QueryAnalyzer(model=self.model) self.context_aggregator = ContextAggregator(model=self.model) self.answer_generator = AnswerGenerator(model=self.model) self.client = OpenAI(api_key=OPENAI_API_KEY) # LegalAgent will be imported on demand def _debug_print(self, message): """Print debug message if debug mode is enabled.""" if self.debug: print(f"\n🧠 AGENT THINKING: {message}") def _enhance_query_with_insights(self, query: str, analysis: Dict[str, Any]) -> str: """ Enhance the original query with insights from analysis to improve retrieval. Args: query: Original user query analysis: Query analysis results Returns: Enhanced query for better retrieval """ try: system_prompt = ( "Eres un experto en búsqueda semántica para investigaciones legales. Tu objetivo es reformular y mejorar " "la consulta original para maximizar la recuperación de información relevante para la defensa legal. " "Debes expandir la consulta con términos técnicos legales, conceptos relacionados, y posibles " "contraargumentos que debería contemplar la defensa." ) analysis_text = analysis.get("analysis", "") response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Consulta original: {query}\n\nAnálisis de la consulta: {analysis_text}\n\n" f"Formula una consulta mejorada que maximice la recuperación de información relevante para " f"la defensa. La consulta debe ser expansiva pero precisa."} ], temperature=0.3 ) enhanced_query = response.choices[0].message.content.strip() # If response is too verbose, extract just the query part if len(enhanced_query.split()) > 30: # Try to find a clear query section import re query_matches = re.search(r"(?:consulta mejorada|consulta refinada|consulta expandida):\s*(.*?)(?:\n|$)", enhanced_query, re.IGNORECASE) if query_matches: enhanced_query = query_matches.group(1).strip() self._debug_print(f"Consulta original: {query}\nConsulta mejorada: {enhanced_query}") return enhanced_query except Exception as e: print(f"Error enhancing query: {e}") return query # Fallback to original query def _extract_key_concepts(self, query: str, context: str) -> List[str]: """ Extract key legal concepts from query and context. Args: query: The user's query context: The retrieved context Returns: List of key legal concepts """ try: system_prompt = ( "Eres un experto en análisis jurídico de primer nivel. Tu tarea es identificar y extraer los conceptos " "legales clave, fundamentos jurídicos y argumentos principales de la información proporcionada. " "Extrae exactamente los 5-7 conceptos más relevantes para la defensa legal, organizados por orden de importancia." ) # Use a preview of the context to avoid token issues context_preview = context[:3000] if len(context) > 3000 else context response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Consulta: {query}\n\nContexto:\n{context_preview}"} ], temperature=0.2 ) concepts_text = response.choices[0].message.content.strip() # Extract concepts as a list import re # Look for numbered or bulleted items concepts = re.findall(r"(?:^|\n)(?:\d+\.\s*|\*\s*|\-\s*)(.*?)(?:$|\n)", concepts_text) # If regex didn't find structured concepts, split by newlines and filter if not concepts: concepts = [line.strip() for line in concepts_text.split("\n") if line.strip() and not line.strip().startswith("#")] # Ensure we have a reasonable number of concepts if len(concepts) > 10: concepts = concepts[:10] elif len(concepts) < 3 and len(concepts_text) > 50: # If we have too few but a lot of text, try line splitting concepts = [line.strip() for line in concepts_text.split("\n") if len(line.strip()) > 10][:7] self._debug_print(f"Conceptos clave extraídos: {concepts}") return concepts except Exception as e: print(f"Error extracting key concepts: {e}") return [] def _refine_context_with_concepts(self, context: str, concepts: List[str], query: str) -> str: """ Refine the context by emphasizing key concepts and restructuring for clarity. Args: context: The original context concepts: Key legal concepts extracted query: The user's query Returns: Refined context """ try: if not concepts: return context system_prompt = ( "Eres un experto legal especializado en reestructurar información para maximizar su utilidad en " "argumentación jurídica. Tu tarea es refinar y reorganizar el contexto proporcionado para:" "\n1. Enfatizar los conceptos clave identificados" "\n2. Organizar la información en una estructura coherente y progresiva" "\n3. Conectar elementos relacionados entre sí" "\n4. Incluir un resumen de alto nivel al inicio que integre los conceptos clave" "\n5. Añadir subtítulos informativos para mejorar la navegabilidad" "\nMantén TODA la información relevante del contexto original." ) # Create a concepts section concepts_text = "\n".join([f"- {concept}" for concept in concepts]) # Only process a portion of the context if it's very large if len(context) > 5000: # Find natural breakpoints to split the context chunks = [] current_pos = 0 while current_pos < len(context): next_chunk_end = min(current_pos + 5000, len(context)) # Try to find a natural breakpoint (like a paragraph end) if next_chunk_end < len(context): breakpoint_search = context[next_chunk_end-200:next_chunk_end+200] paragraph_breaks = [m.start() for m in re.finditer(r'\n\n', breakpoint_search)] if paragraph_breaks: # Find the break closest to the 5000 char mark closest_break = min(paragraph_breaks, key=lambda x: abs(x - 200)) next_chunk_end = next_chunk_end - 200 + closest_break + 2 # +2 for the \n\n chunks.append(context[current_pos:next_chunk_end]) current_pos = next_chunk_end # Process each chunk refined_chunks = [] for i, chunk in enumerate(chunks): try: # Use a simpler prompt for chunk refinement chunk_prompt = ( "Reorganiza este fragmento de texto para enfatizar los conceptos clave, " "manteniendo toda la información relevante. Añade conectores cuando sea necesario " "para mejorar la fluidez y coherencia." ) response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Fragmento {i+1} de {len(chunks)}:\n\n{chunk}\n\n" f"Conceptos clave a enfatizar:\n{concepts_text}"} ], temperature=0.3 ) refined_chunks.append(response.choices[0].message.content.strip()) except Exception as e: print(f"Error refining chunk {i+1}: {e}") refined_chunks.append(chunk) # Use original chunk if refinement fails # Combine refined chunks refined_context = "\n\n".join(refined_chunks) # Add a global summary at the beginning try: summary_prompt = ( "Crea un resumen ejecutivo conciso (máximo 300 palabras) que integre todos los conceptos clave " "y presente una visión de alto nivel de la información más relevante para la defensa." ) summary_response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": "Eres un experto legal que crea resúmenes ejecutivos precisos y orientados a la defensa."}, {"role": "user", "content": f"Consulta: {query}\n\nConceptos clave:\n{concepts_text}\n\n{summary_prompt}"} ], temperature=0.3 ) summary = summary_response.choices[0].message.content.strip() refined_context = f"# Resumen Ejecutivo\n\n{summary}\n\n# Información Detallada\n\n{refined_context}" except Exception as e: print(f"Error creating summary: {e}") # Continue without summary if it fails return refined_context else: # For smaller contexts, process all at once response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Consulta: {query}\n\nConceptos clave a enfatizar:\n{concepts_text}\n\n" f"Contexto a refinar:\n\n{context}"} ], temperature=0.3 ) return response.choices[0].message.content.strip() except Exception as e: print(f"Error refining context: {e}") return context # Return original context if refinement fails def _add_metacognitive_reflection(self, query: str, context: str, concepts: List[str]) -> str: """ Add metacognitive reflections to help guide the answer generation. Args: query: The user's query context: The context concepts: Key legal concepts Returns: Metacognitive guidance for answer generation """ try: system_prompt = ( "Eres un abogado estratega de alto nivel capaz de analizar situaciones legales complejas desde múltiples " "perspectivas. Tu función es proporcionar una guía metacognitiva que:" "\n1. Identifique las principales líneas argumentativas disponibles para la defensa" "\n2. Señale conexiones no obvias entre elementos del contexto que podrían fortalecer la defensa" "\n3. Sugiera perspectivas alternativas que podrían no ser evidentes" "\n4. Identifique áreas donde un contraargumento podría ser necesario" "\n5. Destaque principios legales fundamentales aplicables" "\nEsta guía será utilizada para estructurar una respuesta legal sólida y coherente." ) # Use just a preview of the context context_preview = context[:2000] if len(context) > 2000 else context concepts_text = "\n".join([f"- {concept}" for concept in concepts]) if concepts else "No se identificaron conceptos específicos." response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Consulta de defensa: {query}\n\n" f"Conceptos clave:\n{concepts_text}\n\n" f"Contexto (extracto):\n{context_preview}"} ], temperature=0.4 ) reflection = response.choices[0].message.content.strip() # Format the reflection as a guidance section formatted_reflection = f"## Guía Estratégica para la Respuesta\n\n{reflection}\n\n" formatted_reflection += "## Contexto Completo\n\n" return formatted_reflection except Exception as e: print(f"Error generating metacognitive reflection: {e}") return "" # Return empty string if reflection fails def process_query(self, query: str) -> Dict[str, Any]: """ Process a user query through the agent pipeline. Args: query: The user's query Returns: Dictionary containing the results and intermediate steps """ results = { "original_query": query, "model_used": self.model, "reasoning_steps": [] if self.debug else None } try: # Step 1: Analyze the query self._debug_print("Analizando la consulta...") print("Analizando consulta...") query_analysis = self.query_analyzer.analyze_query(query) if self.debug: # Extract key findings from analysis analysis_text = query_analysis.get("analysis", "") structured_analysis = query_analysis.get("structured_analysis", "") reasoning = f"Análisis de consulta completo:\n" # Add a simplified version of the analysis if structured_analysis: reasoning += f"{structured_analysis}\n" else: reasoning += f"{analysis_text[:300]}...\n" results["reasoning_steps"].append({ "stage": "Análisis de Consulta", "reasoning": reasoning }) self._debug_print(reasoning) results["query_analysis"] = query_analysis # Step 1.5: Enhance query based on analysis for better retrieval enhanced_query = self._enhance_query_with_insights(query, query_analysis) results["enhanced_query"] = enhanced_query if self.debug and enhanced_query != query: reasoning = f"Consulta mejorada para recuperación:\n{enhanced_query}\n" results["reasoning_steps"].append({ "stage": "Mejora de Consulta", "reasoning": reasoning }) # Step 2: Retrieve relevant chunks using the enhanced query self._debug_print("Buscando documentos relevantes...") print("Recuperando documentos para la defensa...") try: # Use the enhanced query for retrieval retrieved_chunks = self.retriever.retrieve(enhanced_query, self.top_k) results["num_chunks_retrieved"] = len(retrieved_chunks) except Exception as e: print(f"Error durante la recuperación: {e}") # Try a simpler approach with fewer chunks print("Probando con parámetros reducidos...") try: retrieved_chunks = self.retriever.retrieve(query, min(5, self.top_k)) # Fallback to original query results["num_chunks_retrieved"] = len(retrieved_chunks) results["retrieval_fallback_used"] = True except Exception as inner_e: print(f"La recuperación falló completamente: {inner_e}") raise if self.debug: # Analyze the retrieved chunks num_chunks = len(retrieved_chunks) source_summary = {} # Count chunks per source for chunk in retrieved_chunks: source = chunk.get('source', 'unknown') if source in source_summary: source_summary[source] += 1 else: source_summary[source] = 1 # Build the reasoning text sources_text = ", ".join([f"{src} ({count})" for src, count in source_summary.items()]) reasoning = f"Recuperados {num_chunks} fragmentos relevantes de fuentes: {sources_text}\n" if num_chunks > 0: # Add preview of top chunks reasoning += f"\nVista previa de resultados prioritarios para la defensa:\n" for i, chunk in enumerate(retrieved_chunks[:3]): chunk_text = chunk.get('text', chunk.get('chunk', 'No content available')) preview = chunk_text[:100] + "..." if len(chunk_text) > 100 else chunk_text reasoning += f"{i+1}. {preview}\n" results["reasoning_steps"].append({ "stage": "Recuperación de Documentos", "reasoning": reasoning }) self._debug_print(reasoning) # Step 3: Aggregate and organize context self._debug_print("Organizando información para construir argumentos de defensa sólidos...") print("Agregando contexto...") try: aggregated_context = self.context_aggregator.aggregate_context(query, retrieved_chunks) results["context_length"] = len(aggregated_context) except Exception as e: print(f"Error durante la agregación de contexto: {e}") # Use a simple fallback context print("Usando agregación simple de contexto como alternativa...") aggregated_context = self.retriever.get_formatted_context(retrieved_chunks) results["context_length"] = len(aggregated_context) results["context_fallback_used"] = True if self.debug: # Analyze the context context_preview = aggregated_context[:200] + "..." if len(aggregated_context) > 200 else aggregated_context word_count = len(aggregated_context.split()) reasoning = f"Organizadas {word_count} palabras de información contextual para generar precisión y claridad.\n" reasoning += f"Vista previa del contexto: {context_preview}\n" results["reasoning_steps"].append({ "stage": "Organización del Contexto", "reasoning": reasoning }) self._debug_print(reasoning) # Step 3.5: Extract key concepts and refine context key_concepts = self._extract_key_concepts(query, aggregated_context) results["key_concepts"] = key_concepts if key_concepts: # Refine the context based on key concepts refined_context = self._refine_context_with_concepts(aggregated_context, key_concepts, query) # Add metacognitive reflection for guidance final_context = self._add_metacognitive_reflection(query, refined_context, key_concepts) + refined_context if self.debug: reasoning = "Contexto refinado basado en conceptos clave:\n" reasoning += f"Conceptos identificados: {', '.join(key_concepts[:5])}" if len(key_concepts) > 5: reasoning += f" y {len(key_concepts) - 5} más" reasoning += "\n" results["reasoning_steps"].append({ "stage": "Refinamiento de Contexto", "reasoning": reasoning }) else: final_context = aggregated_context # Step 4: Generate the answer self._debug_print("Formulando respuesta basados en la evidencia organizada...") print("Generando respuesta...") try: answer = self.answer_generator.generate_answer(query, final_context) except Exception as e: print(f"Error durante la generación de respuesta: {e}") # Use legal agent as fallback print("Usando agente legal alternativo como respaldo...") # Import legal agent here to avoid circular dependencies try: from src.agents.legal_agent import LegalAgent # Create an instance of LegalAgent with defense focus legal_agent = LegalAgent(model=self.model) legal_answer = legal_agent.answer_query(query, 5) # Use just 5 chunks for fallback answer = legal_answer.get("answer", "No se pudo generar un contenido preciso.") results["answer_fallback_used"] = True except Exception as legal_error: print(f"Error usando el agente legal alternativo: {legal_error}") answer = "No se pudo generar un contenido preciso debido a dificultades técnicas." results["answer_fallback_used"] = False results["answer"] = answer # Add sources to results try: results["sources"] = [chunk.get('source', 'unknown') for chunk in retrieved_chunks[:5]] except Exception as e: print(f"Error extrayendo fuentes: {e}") results["sources"] = ["Información de fuentes no disponible"] if self.debug: # Analyze the answer generation answer_preview = answer[:150] + "..." if answer else "No se generó respuesta" reasoning = "Respuesta generada basada en el contexto organizado.\n" reasoning += f"Vista previa: {answer_preview}\n" results["reasoning_steps"].append({ "stage": "Generación de Respuesta", "reasoning": reasoning }) self._debug_print("Generación de respuesta completa.") return results except Exception as e: error_details = traceback.format_exc() print(f"Error en el pipeline de agentes, usando agente legal estándar como alternativa: {e}") print(f"Error detallado: {error_details}") if self.debug: reasoning = f"Se encontró un error: {str(e)}\n" reasoning += "Usando agente legal estándar como alternativa." results["reasoning_steps"].append({ "stage": "Recuperación de Error", "reasoning": reasoning }) self._debug_print(reasoning) # Fall back to the standard legal agent try: print("Usando agente legal alternativo...") # Import legal agent here to avoid circular dependencies try: from src.agents.legal_agent import LegalAgent # Create an instance of LegalAgent with defense focus legal_agent = LegalAgent(model=self.model) legal_agent_result = legal_agent.answer_query(query, self.top_k) results["error"] = str(e) results["answer"] = legal_agent_result.get("answer", "No hay contenido preciso disponible del agente alternativo.") if "sources" in legal_agent_result: results["sources"] = legal_agent_result["sources"] return results except Exception as import_error: print(f"Error importando agente legal: {import_error}") raise except Exception as fallback_error: # Even the fallback failed, return a simple response error_msg = f"Error principal: {e}\nError de alternativa: {fallback_error}" print(f"El agente alternativo también falló: {fallback_error}") results["error"] = error_msg results["answer"] = "Me disculpo, pero estoy teniendo dificultades técnicas procesando su consulta sobre la defensa de Cathy Barriga. Por favor, intente nuevamente más tarde." return results