agentic-defensor / src /agents /agent_director.py
vichudo's picture
feat: increase complexity of agents
fe5c818
Raw
History Blame Contribute Delete
40.3 kB
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