anderson-ufrj commited on
Commit
0c769eb
·
1 Parent(s): d4c25be

feat(chat): create Zumbi integration module for chat flow

Browse files

- Implement clean interface to avoid circular imports
- Add lazy loading for Zumbi agent instance
- Create run_zumbi_investigation function with dados.gov.br support
- Format investigation results for chat responses
- Include open data references in response messages

src/api/routes/chat_zumbi_integration.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Integration module for Zumbi agent in chat flow.
3
+
4
+ This module provides a clean interface to use Zumbi agent from chat
5
+ without causing circular imports.
6
+ """
7
+
8
+ import logging
9
+ from typing import Dict, Any, Optional
10
+ from datetime import datetime
11
+
12
+ from src.agents.zumbi import InvestigatorAgent, InvestigationRequest
13
+ from src.agents.deodoro import AgentContext, AgentStatus
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # Cache for agent instance
18
+ _zumbi_agent_instance: Optional[InvestigatorAgent] = None
19
+
20
+
21
+ async def get_zumbi_agent() -> InvestigatorAgent:
22
+ """
23
+ Get or create Zumbi agent instance with lazy loading.
24
+
25
+ Returns:
26
+ InvestigatorAgent instance
27
+ """
28
+ global _zumbi_agent_instance
29
+
30
+ if _zumbi_agent_instance is None:
31
+ logger.info("Creating new Zumbi agent instance")
32
+ _zumbi_agent_instance = InvestigatorAgent()
33
+ await _zumbi_agent_instance.initialize()
34
+
35
+ return _zumbi_agent_instance
36
+
37
+
38
+ async def run_zumbi_investigation(
39
+ query: str,
40
+ organization_codes: Optional[list] = None,
41
+ enable_open_data: bool = True,
42
+ session_id: Optional[str] = None,
43
+ user_id: Optional[str] = None,
44
+ ) -> Dict[str, Any]:
45
+ """
46
+ Run investigation using Zumbi agent.
47
+
48
+ Args:
49
+ query: Investigation query
50
+ organization_codes: Optional organization codes
51
+ enable_open_data: Enable dados.gov.br integration
52
+ session_id: Session ID
53
+ user_id: User ID
54
+
55
+ Returns:
56
+ Investigation results
57
+ """
58
+ try:
59
+ # Get agent instance
60
+ agent = await get_zumbi_agent()
61
+
62
+ # Create investigation request
63
+ investigation_request = InvestigationRequest(
64
+ query=query,
65
+ organization_codes=organization_codes,
66
+ max_records=50,
67
+ enable_open_data_enrichment=enable_open_data,
68
+ anomaly_types=["price_anomaly", "vendor_concentration", "temporal_patterns"]
69
+ )
70
+
71
+ # Create context
72
+ context = AgentContext(
73
+ investigation_id=f"chat_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
74
+ user_id=user_id or "anonymous",
75
+ correlation_id=session_id or "default"
76
+ )
77
+
78
+ # Create message
79
+ message = {
80
+ "action": "investigate",
81
+ "payload": investigation_request.model_dump()
82
+ }
83
+
84
+ logger.info(f"Starting Zumbi investigation: {query}")
85
+
86
+ # Process investigation
87
+ response = await agent.process(message, context)
88
+
89
+ # Format response
90
+ if response.status == AgentStatus.COMPLETED:
91
+ result = response.result
92
+
93
+ # Extract key information
94
+ investigation_data = {
95
+ "status": "completed",
96
+ "anomalies_found": result.get("metadata", {}).get("anomalies_detected", 0),
97
+ "records_analyzed": result.get("metadata", {}).get("records_analyzed", 0),
98
+ "anomalies": result.get("anomalies", []),
99
+ "summary": result.get("summary", {}),
100
+ "open_data_available": False,
101
+ "related_datasets": []
102
+ }
103
+
104
+ # Check for open data enrichment
105
+ if enable_open_data:
106
+ # Count datasets found
107
+ datasets_found = set()
108
+ for anomaly in investigation_data["anomalies"]:
109
+ evidence = anomaly.get("evidence", {})
110
+ if evidence.get("_open_data_available"):
111
+ investigation_data["open_data_available"] = True
112
+ for dataset in evidence.get("_related_datasets", []):
113
+ datasets_found.add(dataset.get("title", "Unknown"))
114
+
115
+ investigation_data["related_datasets"] = list(datasets_found)
116
+
117
+ return investigation_data
118
+
119
+ else:
120
+ logger.error(f"Zumbi investigation failed: {response.error}")
121
+ return {
122
+ "status": "error",
123
+ "error": response.error or "Investigation failed",
124
+ "anomalies_found": 0,
125
+ "records_analyzed": 0
126
+ }
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error in Zumbi investigation: {e}")
130
+ return {
131
+ "status": "error",
132
+ "error": str(e),
133
+ "anomalies_found": 0,
134
+ "records_analyzed": 0
135
+ }
136
+
137
+
138
+ def format_investigation_message(investigation_data: Dict[str, Any]) -> str:
139
+ """
140
+ Format investigation results for chat response.
141
+
142
+ Args:
143
+ investigation_data: Investigation results
144
+
145
+ Returns:
146
+ Formatted message
147
+ """
148
+ if investigation_data["status"] == "error":
149
+ return f"❌ Erro na investigação: {investigation_data.get('error', 'Erro desconhecido')}"
150
+
151
+ message = "🏹 **Investigação Concluída**\n\n"
152
+ message += f"📊 **Resumo da Análise:**\n"
153
+ message += f"• Registros analisados: {investigation_data['records_analyzed']}\n"
154
+ message += f"• Anomalias detectadas: {investigation_data['anomalies_found']}\n"
155
+
156
+ # Add open data information if available
157
+ if investigation_data.get("open_data_available"):
158
+ datasets_count = len(investigation_data.get("related_datasets", []))
159
+ message += f"• 📂 Datasets abertos encontrados: {datasets_count}\n"
160
+
161
+ message += "\n"
162
+
163
+ # Show anomalies
164
+ if investigation_data["anomalies_found"] > 0:
165
+ message += "⚠️ **Anomalias Detectadas:**\n"
166
+
167
+ for i, anomaly in enumerate(investigation_data["anomalies"][:5], 1):
168
+ severity = anomaly.get("severity", 0)
169
+ severity_emoji = "🔴" if severity > 0.7 else "🟡" if severity > 0.4 else "🟢"
170
+
171
+ message += f"\n{severity_emoji} **{i}. {anomaly.get('anomaly_type', 'Unknown').replace('_', ' ').title()}**\n"
172
+ message += f" • Severidade: {severity:.2f}\n"
173
+ message += f" • {anomaly.get('description', 'Sem descrição')}\n"
174
+
175
+ # Add open data reference if available
176
+ evidence = anomaly.get("evidence", {})
177
+ if evidence.get("_related_datasets"):
178
+ message += f" • 📂 Dados abertos relacionados disponíveis\n"
179
+
180
+ else:
181
+ message += "✅ Nenhuma anomalia significativa foi detectada nos dados analisados.\n"
182
+
183
+ # Add summary statistics if available
184
+ summary = investigation_data.get("summary", {})
185
+ if summary:
186
+ message += f"\n📈 **Estatísticas:**\n"
187
+ if "total_value" in summary:
188
+ message += f"• Valor total analisado: R$ {summary['total_value']:,.2f}\n"
189
+ if "organizations_count" in summary:
190
+ message += f"• Organizações: {summary['organizations_count']}\n"
191
+ if "suppliers_count" in summary:
192
+ message += f"• Fornecedores: {summary['suppliers_count']}\n"
193
+
194
+ # Add note about open data if found
195
+ if investigation_data.get("open_data_available"):
196
+ message += f"\n💡 **Dados Abertos Disponíveis:**\n"
197
+ message += f"Encontramos {len(investigation_data['related_datasets'])} conjuntos de dados relacionados no dados.gov.br "
198
+ message += f"que podem fornecer informações adicionais para sua análise.\n"
199
+
200
+ # List first 3 datasets
201
+ for dataset in investigation_data["related_datasets"][:3]:
202
+ message += f"• {dataset}\n"
203
+
204
+ return message