Upload 77 files
Browse files- app/config.py +9 -6
- app/services/investigator_agent.py +67 -0
app/config.py
CHANGED
|
@@ -23,12 +23,15 @@ class Settings(BaseSettings):
|
|
| 23 |
# Cerebras API for LLM-based entity extraction
|
| 24 |
cerebras_api_key: str = ""
|
| 25 |
|
| 26 |
-
#
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
#
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
class Config:
|
| 34 |
env_file = ".env"
|
|
|
|
| 23 |
# Cerebras API for LLM-based entity extraction
|
| 24 |
cerebras_api_key: str = ""
|
| 25 |
|
| 26 |
+
# NumVerify API for phone lookup (free tier: 100 req/month)
|
| 27 |
+
numverify_api_key: str = ""
|
| 28 |
+
|
| 29 |
+
# CORS
|
| 30 |
+
cors_origins: list[str] = ["*"]
|
| 31 |
+
|
| 32 |
+
# Session cookie
|
| 33 |
+
cookie_secure: bool = True
|
| 34 |
+
cookie_samesite: str = "none"
|
| 35 |
|
| 36 |
class Config:
|
| 37 |
env_file = ".env"
|
app/services/investigator_agent.py
CHANGED
|
@@ -117,6 +117,23 @@ TOOLS = [
|
|
| 117 |
}
|
| 118 |
}
|
| 119 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
{
|
| 121 |
"type": "function",
|
| 122 |
"function": {
|
|
@@ -304,6 +321,9 @@ class InvestigatorAgent:
|
|
| 304 |
elif tool_name == "lookup_cnpj":
|
| 305 |
return await self._lookup_cnpj(arguments.get("cnpj", ""))
|
| 306 |
|
|
|
|
|
|
|
|
|
|
| 307 |
elif tool_name == "web_search":
|
| 308 |
return await self._web_search(
|
| 309 |
arguments.get("query", ""),
|
|
@@ -404,6 +424,53 @@ class InvestigatorAgent:
|
|
| 404 |
|
| 405 |
return "CNPJ não encontrado."
|
| 406 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
async def _web_search(self, query: str, freshness: str) -> str:
|
| 408 |
"""Web search via Lancer"""
|
| 409 |
try:
|
|
|
|
| 117 |
}
|
| 118 |
}
|
| 119 |
},
|
| 120 |
+
{
|
| 121 |
+
"type": "function",
|
| 122 |
+
"function": {
|
| 123 |
+
"name": "lookup_phone",
|
| 124 |
+
"description": "Consultar informações de um número de telefone. Retorna país, operadora, tipo de linha (fixo/móvel) e validade.",
|
| 125 |
+
"parameters": {
|
| 126 |
+
"type": "object",
|
| 127 |
+
"properties": {
|
| 128 |
+
"phone": {
|
| 129 |
+
"type": "string",
|
| 130 |
+
"description": "Número de telefone com código do país (ex: 5511999998888)"
|
| 131 |
+
}
|
| 132 |
+
},
|
| 133 |
+
"required": ["phone"]
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
},
|
| 137 |
{
|
| 138 |
"type": "function",
|
| 139 |
"function": {
|
|
|
|
| 321 |
elif tool_name == "lookup_cnpj":
|
| 322 |
return await self._lookup_cnpj(arguments.get("cnpj", ""))
|
| 323 |
|
| 324 |
+
elif tool_name == "lookup_phone":
|
| 325 |
+
return await self._lookup_phone(arguments.get("phone", ""))
|
| 326 |
+
|
| 327 |
elif tool_name == "web_search":
|
| 328 |
return await self._web_search(
|
| 329 |
arguments.get("query", ""),
|
|
|
|
| 424 |
|
| 425 |
return "CNPJ não encontrado."
|
| 426 |
|
| 427 |
+
async def _lookup_phone(self, phone: str) -> str:
|
| 428 |
+
"""Lookup phone number via NumVerify API"""
|
| 429 |
+
# Clean phone number - keep only digits
|
| 430 |
+
phone_clean = "".join(c for c in phone if c.isdigit())
|
| 431 |
+
|
| 432 |
+
# NumVerify API key (free tier: 100 req/month)
|
| 433 |
+
numverify_key = getattr(settings, 'numverify_api_key', None)
|
| 434 |
+
|
| 435 |
+
if not numverify_key:
|
| 436 |
+
# Fallback: just do a web search for the number
|
| 437 |
+
return await self._web_search(f'"{phone_clean}" telefone', "any")
|
| 438 |
+
|
| 439 |
+
try:
|
| 440 |
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
| 441 |
+
response = await client.get(
|
| 442 |
+
"http://apilayer.net/api/validate",
|
| 443 |
+
params={
|
| 444 |
+
"access_key": numverify_key,
|
| 445 |
+
"number": phone_clean,
|
| 446 |
+
"country_code": "", # Auto-detect
|
| 447 |
+
"format": 1
|
| 448 |
+
}
|
| 449 |
+
)
|
| 450 |
+
|
| 451 |
+
if response.status_code == 200:
|
| 452 |
+
data = response.json()
|
| 453 |
+
|
| 454 |
+
if data.get("valid"):
|
| 455 |
+
result = {
|
| 456 |
+
"numero": data.get("international_format"),
|
| 457 |
+
"valido": True,
|
| 458 |
+
"pais": data.get("country_name"),
|
| 459 |
+
"codigo_pais": data.get("country_code"),
|
| 460 |
+
"operadora": data.get("carrier"),
|
| 461 |
+
"tipo_linha": data.get("line_type"), # mobile, landline, etc
|
| 462 |
+
"localizacao": data.get("location")
|
| 463 |
+
}
|
| 464 |
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
| 465 |
+
else:
|
| 466 |
+
return f"Número {phone_clean} não é válido ou não foi encontrado."
|
| 467 |
+
|
| 468 |
+
return "Erro ao consultar número."
|
| 469 |
+
|
| 470 |
+
except Exception as e:
|
| 471 |
+
# Fallback to web search
|
| 472 |
+
return await self._web_search(f'"{phone_clean}" telefone', "any")
|
| 473 |
+
|
| 474 |
async def _web_search(self, query: str, freshness: str) -> str:
|
| 475 |
"""Web search via Lancer"""
|
| 476 |
try:
|