vichudo commited on
Commit
fe5c818
·
1 Parent(s): 93220ad

feat: increase complexity of agents

Browse files
src/agents/agent_director.py CHANGED
@@ -1,3 +1,4 @@
 
1
  from openai import OpenAI
2
  from typing import List, Dict, Any, Optional, Tuple
3
  import sys
@@ -299,6 +300,7 @@ class AgentDirector:
299
  self.query_analyzer = QueryAnalyzer(model=self.model)
300
  self.context_aggregator = ContextAggregator(model=self.model)
301
  self.answer_generator = AnswerGenerator(model=self.model)
 
302
  # LegalAgent will be imported on demand
303
 
304
  def _debug_print(self, message):
@@ -306,6 +308,281 @@ class AgentDirector:
306
  if self.debug:
307
  print(f"\n🧠 AGENT THINKING: {message}")
308
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  def process_query(self, query: str) -> Dict[str, Any]:
310
  """
311
  Process a user query through the agent pipeline.
@@ -350,19 +627,31 @@ class AgentDirector:
350
 
351
  results["query_analysis"] = query_analysis
352
 
353
- # Step 2: Retrieve relevant chunks
 
 
 
 
 
 
 
 
 
 
 
354
  self._debug_print("Buscando documentos relevantes...")
355
  print("Recuperando documentos para la defensa...")
356
 
357
  try:
358
- retrieved_chunks = self.retriever.retrieve(query, self.top_k)
 
359
  results["num_chunks_retrieved"] = len(retrieved_chunks)
360
  except Exception as e:
361
  print(f"Error durante la recuperación: {e}")
362
  # Try a simpler approach with fewer chunks
363
  print("Probando con parámetros reducidos...")
364
  try:
365
- retrieved_chunks = self.retriever.retrieve(query, min(5, self.top_k))
366
  results["num_chunks_retrieved"] = len(retrieved_chunks)
367
  results["retrieval_fallback_used"] = True
368
  except Exception as inner_e:
@@ -431,11 +720,36 @@ class AgentDirector:
431
 
432
  self._debug_print(reasoning)
433
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  # Step 4: Generate the answer
435
  self._debug_print("Formulando respuesta basados en la evidencia organizada...")
436
  print("Generando respuesta...")
437
  try:
438
- answer = self.answer_generator.generate_answer(query, aggregated_context)
439
  except Exception as e:
440
  print(f"Error durante la generación de respuesta: {e}")
441
  # Use legal agent as fallback
 
1
+ import re
2
  from openai import OpenAI
3
  from typing import List, Dict, Any, Optional, Tuple
4
  import sys
 
300
  self.query_analyzer = QueryAnalyzer(model=self.model)
301
  self.context_aggregator = ContextAggregator(model=self.model)
302
  self.answer_generator = AnswerGenerator(model=self.model)
303
+ self.client = OpenAI(api_key=OPENAI_API_KEY)
304
  # LegalAgent will be imported on demand
305
 
306
  def _debug_print(self, message):
 
308
  if self.debug:
309
  print(f"\n🧠 AGENT THINKING: {message}")
310
 
311
+ def _enhance_query_with_insights(self, query: str, analysis: Dict[str, Any]) -> str:
312
+ """
313
+ Enhance the original query with insights from analysis to improve retrieval.
314
+
315
+ Args:
316
+ query: Original user query
317
+ analysis: Query analysis results
318
+
319
+ Returns:
320
+ Enhanced query for better retrieval
321
+ """
322
+ try:
323
+ system_prompt = (
324
+ "Eres un experto en búsqueda semántica para investigaciones legales. Tu objetivo es reformular y mejorar "
325
+ "la consulta original para maximizar la recuperación de información relevante para la defensa legal. "
326
+ "Debes expandir la consulta con términos técnicos legales, conceptos relacionados, y posibles "
327
+ "contraargumentos que debería contemplar la defensa."
328
+ )
329
+
330
+ analysis_text = analysis.get("analysis", "")
331
+
332
+ response = self.client.chat.completions.create(
333
+ model=self.model,
334
+ messages=[
335
+ {"role": "system", "content": system_prompt},
336
+ {"role": "user", "content": f"Consulta original: {query}\n\nAnálisis de la consulta: {analysis_text}\n\n"
337
+ f"Formula una consulta mejorada que maximice la recuperación de información relevante para "
338
+ f"la defensa. La consulta debe ser expansiva pero precisa."}
339
+ ],
340
+ temperature=0.3
341
+ )
342
+
343
+ enhanced_query = response.choices[0].message.content.strip()
344
+
345
+ # If response is too verbose, extract just the query part
346
+ if len(enhanced_query.split()) > 30:
347
+ # Try to find a clear query section
348
+ import re
349
+ query_matches = re.search(r"(?:consulta mejorada|consulta refinada|consulta expandida):\s*(.*?)(?:\n|$)",
350
+ enhanced_query, re.IGNORECASE)
351
+ if query_matches:
352
+ enhanced_query = query_matches.group(1).strip()
353
+
354
+ self._debug_print(f"Consulta original: {query}\nConsulta mejorada: {enhanced_query}")
355
+ return enhanced_query
356
+ except Exception as e:
357
+ print(f"Error enhancing query: {e}")
358
+ return query # Fallback to original query
359
+
360
+ def _extract_key_concepts(self, query: str, context: str) -> List[str]:
361
+ """
362
+ Extract key legal concepts from query and context.
363
+
364
+ Args:
365
+ query: The user's query
366
+ context: The retrieved context
367
+
368
+ Returns:
369
+ List of key legal concepts
370
+ """
371
+ try:
372
+ system_prompt = (
373
+ "Eres un experto en análisis jurídico de primer nivel. Tu tarea es identificar y extraer los conceptos "
374
+ "legales clave, fundamentos jurídicos y argumentos principales de la información proporcionada. "
375
+ "Extrae exactamente los 5-7 conceptos más relevantes para la defensa legal, organizados por orden de importancia."
376
+ )
377
+
378
+ # Use a preview of the context to avoid token issues
379
+ context_preview = context[:3000] if len(context) > 3000 else context
380
+
381
+ response = self.client.chat.completions.create(
382
+ model=self.model,
383
+ messages=[
384
+ {"role": "system", "content": system_prompt},
385
+ {"role": "user", "content": f"Consulta: {query}\n\nContexto:\n{context_preview}"}
386
+ ],
387
+ temperature=0.2
388
+ )
389
+
390
+ concepts_text = response.choices[0].message.content.strip()
391
+
392
+ # Extract concepts as a list
393
+ import re
394
+ # Look for numbered or bulleted items
395
+ concepts = re.findall(r"(?:^|\n)(?:\d+\.\s*|\*\s*|\-\s*)(.*?)(?:$|\n)", concepts_text)
396
+
397
+ # If regex didn't find structured concepts, split by newlines and filter
398
+ if not concepts:
399
+ concepts = [line.strip() for line in concepts_text.split("\n")
400
+ if line.strip() and not line.strip().startswith("#")]
401
+
402
+ # Ensure we have a reasonable number of concepts
403
+ if len(concepts) > 10:
404
+ concepts = concepts[:10]
405
+ elif len(concepts) < 3 and len(concepts_text) > 50:
406
+ # If we have too few but a lot of text, try line splitting
407
+ concepts = [line.strip() for line in concepts_text.split("\n") if len(line.strip()) > 10][:7]
408
+
409
+ self._debug_print(f"Conceptos clave extraídos: {concepts}")
410
+ return concepts
411
+ except Exception as e:
412
+ print(f"Error extracting key concepts: {e}")
413
+ return []
414
+
415
+ def _refine_context_with_concepts(self, context: str, concepts: List[str], query: str) -> str:
416
+ """
417
+ Refine the context by emphasizing key concepts and restructuring for clarity.
418
+
419
+ Args:
420
+ context: The original context
421
+ concepts: Key legal concepts extracted
422
+ query: The user's query
423
+
424
+ Returns:
425
+ Refined context
426
+ """
427
+ try:
428
+ if not concepts:
429
+ return context
430
+
431
+ system_prompt = (
432
+ "Eres un experto legal especializado en reestructurar información para maximizar su utilidad en "
433
+ "argumentación jurídica. Tu tarea es refinar y reorganizar el contexto proporcionado para:"
434
+ "\n1. Enfatizar los conceptos clave identificados"
435
+ "\n2. Organizar la información en una estructura coherente y progresiva"
436
+ "\n3. Conectar elementos relacionados entre sí"
437
+ "\n4. Incluir un resumen de alto nivel al inicio que integre los conceptos clave"
438
+ "\n5. Añadir subtítulos informativos para mejorar la navegabilidad"
439
+ "\nMantén TODA la información relevante del contexto original."
440
+ )
441
+
442
+ # Create a concepts section
443
+ concepts_text = "\n".join([f"- {concept}" for concept in concepts])
444
+
445
+ # Only process a portion of the context if it's very large
446
+ if len(context) > 5000:
447
+ # Find natural breakpoints to split the context
448
+ chunks = []
449
+ current_pos = 0
450
+ while current_pos < len(context):
451
+ next_chunk_end = min(current_pos + 5000, len(context))
452
+
453
+ # Try to find a natural breakpoint (like a paragraph end)
454
+ if next_chunk_end < len(context):
455
+ breakpoint_search = context[next_chunk_end-200:next_chunk_end+200]
456
+ paragraph_breaks = [m.start() for m in re.finditer(r'\n\n', breakpoint_search)]
457
+
458
+ if paragraph_breaks:
459
+ # Find the break closest to the 5000 char mark
460
+ closest_break = min(paragraph_breaks, key=lambda x: abs(x - 200))
461
+ next_chunk_end = next_chunk_end - 200 + closest_break + 2 # +2 for the \n\n
462
+
463
+ chunks.append(context[current_pos:next_chunk_end])
464
+ current_pos = next_chunk_end
465
+
466
+ # Process each chunk
467
+ refined_chunks = []
468
+ for i, chunk in enumerate(chunks):
469
+ try:
470
+ # Use a simpler prompt for chunk refinement
471
+ chunk_prompt = (
472
+ "Reorganiza este fragmento de texto para enfatizar los conceptos clave, "
473
+ "manteniendo toda la información relevante. Añade conectores cuando sea necesario "
474
+ "para mejorar la fluidez y coherencia."
475
+ )
476
+
477
+ response = self.client.chat.completions.create(
478
+ model=self.model,
479
+ messages=[
480
+ {"role": "system", "content": system_prompt},
481
+ {"role": "user", "content": f"Fragmento {i+1} de {len(chunks)}:\n\n{chunk}\n\n"
482
+ f"Conceptos clave a enfatizar:\n{concepts_text}"}
483
+ ],
484
+ temperature=0.3
485
+ )
486
+
487
+ refined_chunks.append(response.choices[0].message.content.strip())
488
+ except Exception as e:
489
+ print(f"Error refining chunk {i+1}: {e}")
490
+ refined_chunks.append(chunk) # Use original chunk if refinement fails
491
+
492
+ # Combine refined chunks
493
+ refined_context = "\n\n".join(refined_chunks)
494
+
495
+ # Add a global summary at the beginning
496
+ try:
497
+ summary_prompt = (
498
+ "Crea un resumen ejecutivo conciso (máximo 300 palabras) que integre todos los conceptos clave "
499
+ "y presente una visión de alto nivel de la información más relevante para la defensa."
500
+ )
501
+
502
+ summary_response = self.client.chat.completions.create(
503
+ model=self.model,
504
+ messages=[
505
+ {"role": "system", "content": "Eres un experto legal que crea resúmenes ejecutivos precisos y orientados a la defensa."},
506
+ {"role": "user", "content": f"Consulta: {query}\n\nConceptos clave:\n{concepts_text}\n\n{summary_prompt}"}
507
+ ],
508
+ temperature=0.3
509
+ )
510
+
511
+ summary = summary_response.choices[0].message.content.strip()
512
+ refined_context = f"# Resumen Ejecutivo\n\n{summary}\n\n# Información Detallada\n\n{refined_context}"
513
+ except Exception as e:
514
+ print(f"Error creating summary: {e}")
515
+ # Continue without summary if it fails
516
+
517
+ return refined_context
518
+
519
+ else:
520
+ # For smaller contexts, process all at once
521
+ response = self.client.chat.completions.create(
522
+ model=self.model,
523
+ messages=[
524
+ {"role": "system", "content": system_prompt},
525
+ {"role": "user", "content": f"Consulta: {query}\n\nConceptos clave a enfatizar:\n{concepts_text}\n\n"
526
+ f"Contexto a refinar:\n\n{context}"}
527
+ ],
528
+ temperature=0.3
529
+ )
530
+
531
+ return response.choices[0].message.content.strip()
532
+ except Exception as e:
533
+ print(f"Error refining context: {e}")
534
+ return context # Return original context if refinement fails
535
+
536
+ def _add_metacognitive_reflection(self, query: str, context: str, concepts: List[str]) -> str:
537
+ """
538
+ Add metacognitive reflections to help guide the answer generation.
539
+
540
+ Args:
541
+ query: The user's query
542
+ context: The context
543
+ concepts: Key legal concepts
544
+
545
+ Returns:
546
+ Metacognitive guidance for answer generation
547
+ """
548
+ try:
549
+ system_prompt = (
550
+ "Eres un abogado estratega de alto nivel capaz de analizar situaciones legales complejas desde múltiples "
551
+ "perspectivas. Tu función es proporcionar una guía metacognitiva que:"
552
+ "\n1. Identifique las principales líneas argumentativas disponibles para la defensa"
553
+ "\n2. Señale conexiones no obvias entre elementos del contexto que podrían fortalecer la defensa"
554
+ "\n3. Sugiera perspectivas alternativas que podrían no ser evidentes"
555
+ "\n4. Identifique áreas donde un contraargumento podría ser necesario"
556
+ "\n5. Destaque principios legales fundamentales aplicables"
557
+ "\nEsta guía será utilizada para estructurar una respuesta legal sólida y coherente."
558
+ )
559
+
560
+ # Use just a preview of the context
561
+ context_preview = context[:2000] if len(context) > 2000 else context
562
+ concepts_text = "\n".join([f"- {concept}" for concept in concepts]) if concepts else "No se identificaron conceptos específicos."
563
+
564
+ response = self.client.chat.completions.create(
565
+ model=self.model,
566
+ messages=[
567
+ {"role": "system", "content": system_prompt},
568
+ {"role": "user", "content": f"Consulta de defensa: {query}\n\n"
569
+ f"Conceptos clave:\n{concepts_text}\n\n"
570
+ f"Contexto (extracto):\n{context_preview}"}
571
+ ],
572
+ temperature=0.4
573
+ )
574
+
575
+ reflection = response.choices[0].message.content.strip()
576
+
577
+ # Format the reflection as a guidance section
578
+ formatted_reflection = f"## Guía Estratégica para la Respuesta\n\n{reflection}\n\n"
579
+ formatted_reflection += "## Contexto Completo\n\n"
580
+
581
+ return formatted_reflection
582
+ except Exception as e:
583
+ print(f"Error generating metacognitive reflection: {e}")
584
+ return "" # Return empty string if reflection fails
585
+
586
  def process_query(self, query: str) -> Dict[str, Any]:
587
  """
588
  Process a user query through the agent pipeline.
 
627
 
628
  results["query_analysis"] = query_analysis
629
 
630
+ # Step 1.5: Enhance query based on analysis for better retrieval
631
+ enhanced_query = self._enhance_query_with_insights(query, query_analysis)
632
+ results["enhanced_query"] = enhanced_query
633
+
634
+ if self.debug and enhanced_query != query:
635
+ reasoning = f"Consulta mejorada para recuperación:\n{enhanced_query}\n"
636
+ results["reasoning_steps"].append({
637
+ "stage": "Mejora de Consulta",
638
+ "reasoning": reasoning
639
+ })
640
+
641
+ # Step 2: Retrieve relevant chunks using the enhanced query
642
  self._debug_print("Buscando documentos relevantes...")
643
  print("Recuperando documentos para la defensa...")
644
 
645
  try:
646
+ # Use the enhanced query for retrieval
647
+ retrieved_chunks = self.retriever.retrieve(enhanced_query, self.top_k)
648
  results["num_chunks_retrieved"] = len(retrieved_chunks)
649
  except Exception as e:
650
  print(f"Error durante la recuperación: {e}")
651
  # Try a simpler approach with fewer chunks
652
  print("Probando con parámetros reducidos...")
653
  try:
654
+ retrieved_chunks = self.retriever.retrieve(query, min(5, self.top_k)) # Fallback to original query
655
  results["num_chunks_retrieved"] = len(retrieved_chunks)
656
  results["retrieval_fallback_used"] = True
657
  except Exception as inner_e:
 
720
 
721
  self._debug_print(reasoning)
722
 
723
+ # Step 3.5: Extract key concepts and refine context
724
+ key_concepts = self._extract_key_concepts(query, aggregated_context)
725
+ results["key_concepts"] = key_concepts
726
+
727
+ if key_concepts:
728
+ # Refine the context based on key concepts
729
+ refined_context = self._refine_context_with_concepts(aggregated_context, key_concepts, query)
730
+
731
+ # Add metacognitive reflection for guidance
732
+ final_context = self._add_metacognitive_reflection(query, refined_context, key_concepts) + refined_context
733
+
734
+ if self.debug:
735
+ reasoning = "Contexto refinado basado en conceptos clave:\n"
736
+ reasoning += f"Conceptos identificados: {', '.join(key_concepts[:5])}"
737
+ if len(key_concepts) > 5:
738
+ reasoning += f" y {len(key_concepts) - 5} más"
739
+ reasoning += "\n"
740
+
741
+ results["reasoning_steps"].append({
742
+ "stage": "Refinamiento de Contexto",
743
+ "reasoning": reasoning
744
+ })
745
+ else:
746
+ final_context = aggregated_context
747
+
748
  # Step 4: Generate the answer
749
  self._debug_print("Formulando respuesta basados en la evidencia organizada...")
750
  print("Generando respuesta...")
751
  try:
752
+ answer = self.answer_generator.generate_answer(query, final_context)
753
  except Exception as e:
754
  print(f"Error durante la generación de respuesta: {e}")
755
  # Use legal agent as fallback
src/agents/legal_agent.py CHANGED
@@ -70,16 +70,25 @@ class LegalAgent:
70
  # 2. Format the context for the LLM
71
  context = self.retriever.get_formatted_context(retrieved_chunks)
72
 
73
- # 3. Build the system prompt
74
- system_prompt = self._get_legal_system_prompt(context, query)
75
 
76
- # 4. Prepare messages for the chat completion
 
 
 
 
 
 
 
 
 
77
  messages = [
78
  {"role": "system", "content": system_prompt},
79
  {"role": "user", "content": query}
80
  ]
81
 
82
- # 5. Generate the answer
83
  response = self.client.chat.completions.create(
84
  model=self.model,
85
  messages=messages,
@@ -89,10 +98,11 @@ class LegalAgent:
89
 
90
  answer = response.choices[0].message.content
91
 
92
- # 6. Build result dictionary
93
  result = {
94
  "answer": answer,
95
- "sources": [chunk.get('source', 'desconocido') for chunk in retrieved_chunks[:5]]
 
96
  }
97
 
98
  return result
@@ -105,4 +115,174 @@ class LegalAgent:
105
  return {
106
  "answer": f"I apologize, but I encountered an error while processing your legal query. This is likely due to the test mode of the application. In a full deployment, I would provide a detailed legal analysis based on relevant precedents and statutes.\n\nYour query was: {query}\n\nError details: {str(e)}",
107
  "sources": ["Error - no sources available"]
108
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  # 2. Format the context for the LLM
71
  context = self.retriever.get_formatted_context(retrieved_chunks)
72
 
73
+ # 3. Extract key concepts for deeper analysis
74
+ key_concepts = self._extract_key_concepts(query, context)
75
 
76
+ # 4. Enhance context with concept highlighting and reorganization
77
+ enhanced_context = self._enhance_context_with_concepts(context, key_concepts, query)
78
+
79
+ # 5. Add metacognitive reflection to guide reasoning
80
+ final_context = self._add_strategic_guidance(query, enhanced_context, key_concepts) + enhanced_context
81
+
82
+ # 6. Build the system prompt
83
+ system_prompt = self._get_legal_system_prompt(final_context, query)
84
+
85
+ # 7. Prepare messages for the chat completion
86
  messages = [
87
  {"role": "system", "content": system_prompt},
88
  {"role": "user", "content": query}
89
  ]
90
 
91
+ # 8. Generate the answer
92
  response = self.client.chat.completions.create(
93
  model=self.model,
94
  messages=messages,
 
98
 
99
  answer = response.choices[0].message.content
100
 
101
+ # 9. Build result dictionary
102
  result = {
103
  "answer": answer,
104
+ "sources": [chunk.get('source', 'desconocido') for chunk in retrieved_chunks[:5]],
105
+ "key_concepts": key_concepts
106
  }
107
 
108
  return result
 
115
  return {
116
  "answer": f"I apologize, but I encountered an error while processing your legal query. This is likely due to the test mode of the application. In a full deployment, I would provide a detailed legal analysis based on relevant precedents and statutes.\n\nYour query was: {query}\n\nError details: {str(e)}",
117
  "sources": ["Error - no sources available"]
118
+ }
119
+
120
+ def _extract_key_concepts(self, query: str, context: str) -> List[str]:
121
+ """
122
+ Extract key legal concepts from query and context to guide analysis.
123
+
124
+ Args:
125
+ query: The user's query
126
+ context: The retrieved context
127
+
128
+ Returns:
129
+ List of key legal concepts
130
+ """
131
+ try:
132
+ system_prompt = (
133
+ "Eres un experto en análisis jurídico especializado en derecho penal y defensa legal. "
134
+ "Tu tarea es identificar y extraer los conceptos legales clave, fundamentos jurídicos "
135
+ "y argumentos principales de la información proporcionada. Enfócate específicamente en "
136
+ "conceptos relevantes para la defensa de Cathy Barriga. "
137
+ "Extrae entre 5-8 conceptos clave organizados por orden de importancia para la defensa."
138
+ )
139
+
140
+ # Use a preview of the context to avoid token issues
141
+ context_preview = context[:3000] if len(context) > 3000 else context
142
+
143
+ response = self.client.chat.completions.create(
144
+ model=self.model,
145
+ messages=[
146
+ {"role": "system", "content": system_prompt},
147
+ {"role": "user", "content": f"Consulta legal: {query}\n\nContexto legal:\n{context_preview}"}
148
+ ],
149
+ temperature=0.2
150
+ )
151
+
152
+ concepts_text = response.choices[0].message.content.strip()
153
+
154
+ # Extract concepts as a list
155
+ import re
156
+ # Look for numbered or bulleted items
157
+ concepts = re.findall(r"(?:^|\n)(?:\d+\.\s*|\*\s*|\-\s*)(.*?)(?:$|\n)", concepts_text)
158
+
159
+ # If regex didn't find structured concepts, split by newlines and filter
160
+ if not concepts:
161
+ concepts = [line.strip() for line in concepts_text.split("\n")
162
+ if line.strip() and not line.strip().startswith("#")]
163
+
164
+ # Ensure reasonable number of concepts
165
+ if len(concepts) > 10:
166
+ concepts = concepts[:10]
167
+ elif len(concepts) < 3 and len(concepts_text) > 50:
168
+ # If too few but a lot of text, try line splitting
169
+ concepts = [line.strip() for line in concepts_text.split("\n") if len(line.strip()) > 10][:7]
170
+
171
+ return concepts
172
+ except Exception as e:
173
+ print(f"Error extracting key concepts: {e}")
174
+ return [] # Return empty list if extraction fails
175
+
176
+ def _enhance_context_with_concepts(self, context: str, concepts: List[str], query: str) -> str:
177
+ """
178
+ Enhance context by emphasizing key concepts and improving structure.
179
+
180
+ Args:
181
+ context: The original context
182
+ concepts: Key legal concepts extracted
183
+ query: The user's query
184
+
185
+ Returns:
186
+ Enhanced context
187
+ """
188
+ try:
189
+ if not concepts:
190
+ return context
191
+
192
+ system_prompt = (
193
+ "Eres un experto legal especializado en estructurar información para maximizar su utilidad "
194
+ "en estrategias de defensa penal. Tu tarea es reorganizar el contexto proporcionado para:"
195
+ "\n1. Enfatizar los conceptos clave identificados relevantes para la defensa de Cathy Barriga"
196
+ "\n2. Organizar la información en una estructura lógica y progresiva"
197
+ "\n3. Conectar elementos relacionados que puedan fortalecer la defensa"
198
+ "\n4. Incluir un resumen inicial que integre los conceptos fundamentales"
199
+ "\n5. Destacar elementos exculpatorios o mitigantes"
200
+ "\nMantén TODA la información relevante del contexto original."
201
+ )
202
+
203
+ # Create a concepts section
204
+ concepts_text = "\n".join([f"- {concept}" for concept in concepts])
205
+
206
+ # For large contexts, process a reasonable portion
207
+ if len(context) > 6000:
208
+ context_preview = context[:6000]
209
+ response = self.client.chat.completions.create(
210
+ model=self.model,
211
+ messages=[
212
+ {"role": "system", "content": system_prompt},
213
+ {"role": "user", "content": f"Consulta legal: {query}\n\nConceptos clave a enfatizar:\n{concepts_text}\n\n"
214
+ f"Contexto a mejorar (extracto):\n\n{context_preview}"}
215
+ ],
216
+ temperature=0.3
217
+ )
218
+
219
+ enhanced_preview = response.choices[0].message.content.strip()
220
+
221
+ # Add notice about partial enhancement
222
+ return enhanced_preview + "\n\n--- CONTEXTO ADICIONAL ---\n\n" + context[6000:]
223
+ else:
224
+ # For smaller contexts, process all at once
225
+ response = self.client.chat.completions.create(
226
+ model=self.model,
227
+ messages=[
228
+ {"role": "system", "content": system_prompt},
229
+ {"role": "user", "content": f"Consulta legal: {query}\n\nConceptos clave a enfatizar:\n{concepts_text}\n\n"
230
+ f"Contexto a mejorar:\n\n{context}"}
231
+ ],
232
+ temperature=0.3
233
+ )
234
+
235
+ return response.choices[0].message.content.strip()
236
+ except Exception as e:
237
+ print(f"Error enhancing context: {e}")
238
+ return context # Return original context if enhancement fails
239
+
240
+ def _add_strategic_guidance(self, query: str, context: str, concepts: List[str]) -> str:
241
+ """
242
+ Add strategic guidance to direct reasoning and defense strategy.
243
+
244
+ Args:
245
+ query: The user's query
246
+ context: The enhanced context
247
+ concepts: Key legal concepts
248
+
249
+ Returns:
250
+ Strategic guidance section to prepend to context
251
+ """
252
+ try:
253
+ system_prompt = (
254
+ "Eres un estratega legal de elite especializado en defensa penal. Tu función es proporcionar "
255
+ "una guía estratégica que:"
256
+ "\n1. Identifique las principales líneas argumentativas disponibles para la defensa de Cathy Barriga"
257
+ "\n2. Señale conexiones no evidentes entre elementos del contexto que podrían fortalecer la defensa"
258
+ "\n3. Identifique debilidades en la argumentación acusatoria"
259
+ "\n4. Sugiera enfoques interpretativos alternativos favorables"
260
+ "\n5. Destaque principios legales fundamentales aplicables a la defensa"
261
+ "\nEsta guía orientará la estrategia de defensa."
262
+ )
263
+
264
+ # Use a preview of the context
265
+ context_preview = context[:2000] if len(context) > 2000 else context
266
+ concepts_text = "\n".join([f"- {concept}" for concept in concepts]) if concepts else "No se identificaron conceptos específicos."
267
+
268
+ response = self.client.chat.completions.create(
269
+ model=self.model,
270
+ messages=[
271
+ {"role": "system", "content": system_prompt},
272
+ {"role": "user", "content": f"Consulta para la defensa de Cathy Barriga: {query}\n\n"
273
+ f"Conceptos legales clave:\n{concepts_text}\n\n"
274
+ f"Contexto (extracto):\n{context_preview}"}
275
+ ],
276
+ temperature=0.4
277
+ )
278
+
279
+ guidance = response.choices[0].message.content.strip()
280
+
281
+ # Format the guidance as a distinct section
282
+ formatted_guidance = f"## Guía Estratégica para la Defensa\n\n{guidance}\n\n"
283
+ formatted_guidance += "## Análisis Detallado\n\n"
284
+
285
+ return formatted_guidance
286
+ except Exception as e:
287
+ print(f"Error generating strategic guidance: {e}")
288
+ return "" # Return empty string if guidance generation fails