Wilker commited on
Commit ·
dbb524c
1
Parent(s): 5ccd423
fix(app): restaura suporte a tool calling sem SyntaxError
Browse files
app.py
CHANGED
|
@@ -787,6 +787,9 @@ def chat_completions(req: ChatRequest):
|
|
| 787 |
"max_tokens": max_tokens,
|
| 788 |
"temperature": temperature if temperature else 0.0,
|
| 789 |
}
|
|
|
|
|
|
|
|
|
|
| 790 |
|
| 791 |
@async_retry_with_backoff(max_retries=3, initial_delay=1, backoff_factor=2)
|
| 792 |
async def call_openrouter_api():
|
|
@@ -815,7 +818,9 @@ def chat_completions(req: ChatRequest):
|
|
| 815 |
if "choices" not in data or not data["choices"]:
|
| 816 |
raise APIError(f"Resposta inválida da OpenRouter API: {data}")
|
| 817 |
|
| 818 |
-
|
|
|
|
|
|
|
| 819 |
usage = data.get("usage", {})
|
| 820 |
prompt_tokens = usage.get("prompt_tokens", 0)
|
| 821 |
completion_tokens = usage.get("completion_tokens", 0)
|
|
@@ -836,6 +841,9 @@ def chat_completions(req: ChatRequest):
|
|
| 836 |
"max_tokens": max_tokens,
|
| 837 |
"temperature": temperature if temperature else 0.0,
|
| 838 |
}
|
|
|
|
|
|
|
|
|
|
| 839 |
|
| 840 |
payload_bytes = len(json.dumps(payload))
|
| 841 |
logger.info(f"[GROQ DEBUG] Payload size: {payload_bytes} bytes, Messages: {len(chat)}")
|
|
@@ -867,7 +875,9 @@ def chat_completions(req: ChatRequest):
|
|
| 867 |
if "choices" not in data or not data["choices"]:
|
| 868 |
raise APIError(f"Resposta inválida da Groq API: {data}")
|
| 869 |
|
| 870 |
-
|
|
|
|
|
|
|
| 871 |
usage = data.get("usage", {})
|
| 872 |
prompt_tokens = usage.get("prompt_tokens", 0)
|
| 873 |
completion_tokens = usage.get("completion_tokens", 0)
|
|
@@ -888,6 +898,9 @@ def chat_completions(req: ChatRequest):
|
|
| 888 |
"max_tokens": max_tokens,
|
| 889 |
"temperature": temperature if temperature else 0.0,
|
| 890 |
}
|
|
|
|
|
|
|
|
|
|
| 891 |
|
| 892 |
@async_retry_with_backoff(max_retries=3, initial_delay=1, backoff_factor=2)
|
| 893 |
async def call_mistral_api():
|
|
@@ -916,7 +929,9 @@ def chat_completions(req: ChatRequest):
|
|
| 916 |
if "choices" not in data or not data["choices"]:
|
| 917 |
raise APIError(f"Resposta inválida da Mistral API: {data}")
|
| 918 |
|
| 919 |
-
|
|
|
|
|
|
|
| 920 |
usage = data.get("usage", {})
|
| 921 |
prompt_tokens = usage.get("prompt_tokens", 0)
|
| 922 |
completion_tokens = usage.get("completion_tokens", 0)
|
|
@@ -935,6 +950,9 @@ def chat_completions(req: ChatRequest):
|
|
| 935 |
"temperature": temperature if temperature else 0.0,
|
| 936 |
}
|
| 937 |
|
|
|
|
|
|
|
|
|
|
| 938 |
if model_id.startswith("gemini"):
|
| 939 |
gemini_payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
| 940 |
|
|
@@ -984,6 +1002,25 @@ def chat_completions(req: ChatRequest):
|
|
| 984 |
elapsed = time.time() - t0
|
| 985 |
logger.info(f"Resposta gerada em {elapsed:.1f}s ({backend}, web={req.web})")
|
| 986 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 987 |
return {
|
| 988 |
"id": f"chatcmpl-{uuid.uuid4().hex[:8]}",
|
| 989 |
"object": "chat.completion",
|
|
@@ -992,8 +1029,8 @@ def chat_completions(req: ChatRequest):
|
|
| 992 |
"mode": req.mode,
|
| 993 |
"choices": [{
|
| 994 |
"index": 0,
|
| 995 |
-
"message":
|
| 996 |
-
"finish_reason": "stop"
|
| 997 |
}],
|
| 998 |
"usage": {
|
| 999 |
"prompt_tokens": prompt_tokens,
|
|
@@ -1310,6 +1347,9 @@ async def chat_completions_async(req: ChatRequest):
|
|
| 1310 |
"max_tokens": max_tokens,
|
| 1311 |
"temperature": temperature if temperature else 0.0,
|
| 1312 |
}
|
|
|
|
|
|
|
|
|
|
| 1313 |
|
| 1314 |
async with httpx.AsyncClient() as client:
|
| 1315 |
resp = await client.post(
|
|
@@ -1334,7 +1374,9 @@ async def chat_completions_async(req: ChatRequest):
|
|
| 1334 |
if "choices" not in data or not data["choices"]:
|
| 1335 |
raise APIError(f"Resposta inválida da OpenRouter API: {data}")
|
| 1336 |
|
| 1337 |
-
|
|
|
|
|
|
|
| 1338 |
usage = data.get("usage", {})
|
| 1339 |
prompt_tokens = usage.get("prompt_tokens", 0)
|
| 1340 |
completion_tokens = usage.get("completion_tokens", 0)
|
|
@@ -1355,6 +1397,9 @@ async def chat_completions_async(req: ChatRequest):
|
|
| 1355 |
"max_tokens": max_tokens,
|
| 1356 |
"temperature": temperature if temperature else 0.0,
|
| 1357 |
}
|
|
|
|
|
|
|
|
|
|
| 1358 |
|
| 1359 |
payload_bytes = len(json.dumps(payload))
|
| 1360 |
logger.info(f"[GROQ DEBUG] Payload size: {payload_bytes} bytes, Messages: {len(chat)}")
|
|
@@ -1382,7 +1427,9 @@ async def chat_completions_async(req: ChatRequest):
|
|
| 1382 |
if "choices" not in data or not data["choices"]:
|
| 1383 |
raise APIError(f"Resposta inválida da Groq API: {data}")
|
| 1384 |
|
| 1385 |
-
|
|
|
|
|
|
|
| 1386 |
usage = data.get("usage", {})
|
| 1387 |
prompt_tokens = usage.get("prompt_tokens", 0)
|
| 1388 |
completion_tokens = usage.get("completion_tokens", 0)
|
|
@@ -1403,6 +1450,9 @@ async def chat_completions_async(req: ChatRequest):
|
|
| 1403 |
"max_tokens": max_tokens,
|
| 1404 |
"temperature": temperature if temperature else 0.0,
|
| 1405 |
}
|
|
|
|
|
|
|
|
|
|
| 1406 |
|
| 1407 |
async with httpx.AsyncClient() as client:
|
| 1408 |
resp = await client.post(
|
|
@@ -1446,6 +1496,9 @@ async def chat_completions_async(req: ChatRequest):
|
|
| 1446 |
"temperature": temperature if temperature else 0.0,
|
| 1447 |
}
|
| 1448 |
|
|
|
|
|
|
|
|
|
|
| 1449 |
if model_id.startswith("gemini"):
|
| 1450 |
gemini_payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
| 1451 |
|
|
@@ -1476,7 +1529,10 @@ async def chat_completions_async(req: ChatRequest):
|
|
| 1476 |
|
| 1477 |
logger.info(f"[GEMINI DEBUG] finishReason={data['candidates'][0].get('finishReason')}, content={data['candidates'][0].get('content', {})}")
|
| 1478 |
|
| 1479 |
-
|
|
|
|
|
|
|
|
|
|
| 1480 |
usage = data.get("usageMetadata", {})
|
| 1481 |
prompt_tokens = usage.get("promptTokenCount", 0)
|
| 1482 |
completion_tokens = usage.get("candidatesTokenCount", 0)
|
|
@@ -1491,6 +1547,25 @@ async def chat_completions_async(req: ChatRequest):
|
|
| 1491 |
elapsed = time.time() - t0
|
| 1492 |
logger.info(f"Resposta gerada em {elapsed:.1f}s ({backend}, web={req.web})")
|
| 1493 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1494 |
return {
|
| 1495 |
"id": f"chatcmpl-{uuid.uuid4().hex[:8]}",
|
| 1496 |
"object": "chat.completion",
|
|
@@ -1499,8 +1574,8 @@ async def chat_completions_async(req: ChatRequest):
|
|
| 1499 |
"mode": req.mode,
|
| 1500 |
"choices": [{
|
| 1501 |
"index": 0,
|
| 1502 |
-
"message":
|
| 1503 |
-
"finish_reason": "stop"
|
| 1504 |
}],
|
| 1505 |
"usage": {
|
| 1506 |
"prompt_tokens": prompt_tokens,
|
|
|
|
| 787 |
"max_tokens": max_tokens,
|
| 788 |
"temperature": temperature if temperature else 0.0,
|
| 789 |
}
|
| 790 |
+
if req.tools:
|
| 791 |
+
payload["tools"] = [t.model_dump(exclude_none=True) for t in req.tools]
|
| 792 |
+
payload["tool_choice"] = req.tool_choice or "auto"
|
| 793 |
|
| 794 |
@async_retry_with_backoff(max_retries=3, initial_delay=1, backoff_factor=2)
|
| 795 |
async def call_openrouter_api():
|
|
|
|
| 818 |
if "choices" not in data or not data["choices"]:
|
| 819 |
raise APIError(f"Resposta inválida da OpenRouter API: {data}")
|
| 820 |
|
| 821 |
+
message_obj = data["choices"][0]["message"]
|
| 822 |
+
text = message_obj.get("content", "") or ""
|
| 823 |
+
tool_calls = message_obj.get("tool_calls")
|
| 824 |
usage = data.get("usage", {})
|
| 825 |
prompt_tokens = usage.get("prompt_tokens", 0)
|
| 826 |
completion_tokens = usage.get("completion_tokens", 0)
|
|
|
|
| 841 |
"max_tokens": max_tokens,
|
| 842 |
"temperature": temperature if temperature else 0.0,
|
| 843 |
}
|
| 844 |
+
if req.tools:
|
| 845 |
+
payload["tools"] = [t.model_dump(exclude_none=True) for t in req.tools]
|
| 846 |
+
payload["tool_choice"] = req.tool_choice or "auto"
|
| 847 |
|
| 848 |
payload_bytes = len(json.dumps(payload))
|
| 849 |
logger.info(f"[GROQ DEBUG] Payload size: {payload_bytes} bytes, Messages: {len(chat)}")
|
|
|
|
| 875 |
if "choices" not in data or not data["choices"]:
|
| 876 |
raise APIError(f"Resposta inválida da Groq API: {data}")
|
| 877 |
|
| 878 |
+
message_obj = data["choices"][0]["message"]
|
| 879 |
+
text = message_obj.get("content", "") or ""
|
| 880 |
+
tool_calls = message_obj.get("tool_calls")
|
| 881 |
usage = data.get("usage", {})
|
| 882 |
prompt_tokens = usage.get("prompt_tokens", 0)
|
| 883 |
completion_tokens = usage.get("completion_tokens", 0)
|
|
|
|
| 898 |
"max_tokens": max_tokens,
|
| 899 |
"temperature": temperature if temperature else 0.0,
|
| 900 |
}
|
| 901 |
+
if req.tools:
|
| 902 |
+
payload["tools"] = [t.model_dump(exclude_none=True) for t in req.tools]
|
| 903 |
+
payload["tool_choice"] = req.tool_choice or "auto"
|
| 904 |
|
| 905 |
@async_retry_with_backoff(max_retries=3, initial_delay=1, backoff_factor=2)
|
| 906 |
async def call_mistral_api():
|
|
|
|
| 929 |
if "choices" not in data or not data["choices"]:
|
| 930 |
raise APIError(f"Resposta inválida da Mistral API: {data}")
|
| 931 |
|
| 932 |
+
message_obj = data["choices"][0]["message"]
|
| 933 |
+
text = message_obj.get("content", "") or ""
|
| 934 |
+
tool_calls = message_obj.get("tool_calls")
|
| 935 |
usage = data.get("usage", {})
|
| 936 |
prompt_tokens = usage.get("prompt_tokens", 0)
|
| 937 |
completion_tokens = usage.get("completion_tokens", 0)
|
|
|
|
| 950 |
"temperature": temperature if temperature else 0.0,
|
| 951 |
}
|
| 952 |
|
| 953 |
+
if req.tools:
|
| 954 |
+
gemini_payload["tools"] = convert_tools_to_gemini_format(req.tools)
|
| 955 |
+
|
| 956 |
if model_id.startswith("gemini"):
|
| 957 |
gemini_payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
| 958 |
|
|
|
|
| 1002 |
elapsed = time.time() - t0
|
| 1003 |
logger.info(f"Resposta gerada em {elapsed:.1f}s ({backend}, web={req.web})")
|
| 1004 |
|
| 1005 |
+
message = {"role": "assistant", "content": text}
|
| 1006 |
+
openai_tool_calls = None
|
| 1007 |
+
if 'tool_calls' in locals() and tool_calls:
|
| 1008 |
+
openai_tool_calls = tool_calls
|
| 1009 |
+
elif 'function_calls' in locals() and function_calls:
|
| 1010 |
+
openai_tool_calls = [
|
| 1011 |
+
{
|
| 1012 |
+
"id": f"call_{uuid.uuid4().hex[:8]}",
|
| 1013 |
+
"type": "function",
|
| 1014 |
+
"function": {
|
| 1015 |
+
"name": fc["name"],
|
| 1016 |
+
"arguments": json.dumps(fc.get("args", {})),
|
| 1017 |
+
},
|
| 1018 |
+
}
|
| 1019 |
+
for fc in function_calls
|
| 1020 |
+
]
|
| 1021 |
+
if openai_tool_calls:
|
| 1022 |
+
message["tool_calls"] = openai_tool_calls
|
| 1023 |
+
|
| 1024 |
return {
|
| 1025 |
"id": f"chatcmpl-{uuid.uuid4().hex[:8]}",
|
| 1026 |
"object": "chat.completion",
|
|
|
|
| 1029 |
"mode": req.mode,
|
| 1030 |
"choices": [{
|
| 1031 |
"index": 0,
|
| 1032 |
+
"message": message,
|
| 1033 |
+
"finish_reason": "tool_calls" if openai_tool_calls else (finish_reason or "stop"),
|
| 1034 |
}],
|
| 1035 |
"usage": {
|
| 1036 |
"prompt_tokens": prompt_tokens,
|
|
|
|
| 1347 |
"max_tokens": max_tokens,
|
| 1348 |
"temperature": temperature if temperature else 0.0,
|
| 1349 |
}
|
| 1350 |
+
if req.tools:
|
| 1351 |
+
payload["tools"] = [t.model_dump(exclude_none=True) for t in req.tools]
|
| 1352 |
+
payload["tool_choice"] = req.tool_choice or "auto"
|
| 1353 |
|
| 1354 |
async with httpx.AsyncClient() as client:
|
| 1355 |
resp = await client.post(
|
|
|
|
| 1374 |
if "choices" not in data or not data["choices"]:
|
| 1375 |
raise APIError(f"Resposta inválida da OpenRouter API: {data}")
|
| 1376 |
|
| 1377 |
+
message_obj = data["choices"][0]["message"]
|
| 1378 |
+
text = message_obj.get("content", "") or ""
|
| 1379 |
+
tool_calls = message_obj.get("tool_calls")
|
| 1380 |
usage = data.get("usage", {})
|
| 1381 |
prompt_tokens = usage.get("prompt_tokens", 0)
|
| 1382 |
completion_tokens = usage.get("completion_tokens", 0)
|
|
|
|
| 1397 |
"max_tokens": max_tokens,
|
| 1398 |
"temperature": temperature if temperature else 0.0,
|
| 1399 |
}
|
| 1400 |
+
if req.tools:
|
| 1401 |
+
payload["tools"] = [t.model_dump(exclude_none=True) for t in req.tools]
|
| 1402 |
+
payload["tool_choice"] = req.tool_choice or "auto"
|
| 1403 |
|
| 1404 |
payload_bytes = len(json.dumps(payload))
|
| 1405 |
logger.info(f"[GROQ DEBUG] Payload size: {payload_bytes} bytes, Messages: {len(chat)}")
|
|
|
|
| 1427 |
if "choices" not in data or not data["choices"]:
|
| 1428 |
raise APIError(f"Resposta inválida da Groq API: {data}")
|
| 1429 |
|
| 1430 |
+
message_obj = data["choices"][0]["message"]
|
| 1431 |
+
text = message_obj.get("content", "") or ""
|
| 1432 |
+
tool_calls = message_obj.get("tool_calls")
|
| 1433 |
usage = data.get("usage", {})
|
| 1434 |
prompt_tokens = usage.get("prompt_tokens", 0)
|
| 1435 |
completion_tokens = usage.get("completion_tokens", 0)
|
|
|
|
| 1450 |
"max_tokens": max_tokens,
|
| 1451 |
"temperature": temperature if temperature else 0.0,
|
| 1452 |
}
|
| 1453 |
+
if req.tools:
|
| 1454 |
+
payload["tools"] = [t.model_dump(exclude_none=True) for t in req.tools]
|
| 1455 |
+
payload["tool_choice"] = req.tool_choice or "auto"
|
| 1456 |
|
| 1457 |
async with httpx.AsyncClient() as client:
|
| 1458 |
resp = await client.post(
|
|
|
|
| 1496 |
"temperature": temperature if temperature else 0.0,
|
| 1497 |
}
|
| 1498 |
|
| 1499 |
+
if req.tools:
|
| 1500 |
+
gemini_payload["tools"] = convert_tools_to_gemini_format(req.tools)
|
| 1501 |
+
|
| 1502 |
if model_id.startswith("gemini"):
|
| 1503 |
gemini_payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
| 1504 |
|
|
|
|
| 1529 |
|
| 1530 |
logger.info(f"[GEMINI DEBUG] finishReason={data['candidates'][0].get('finishReason')}, content={data['candidates'][0].get('content', {})}")
|
| 1531 |
|
| 1532 |
+
parts = data["candidates"][0]["content"]["parts"]
|
| 1533 |
+
text_parts = [p.get("text", "") for p in parts if "text" in p]
|
| 1534 |
+
text = "".join(text_parts)
|
| 1535 |
+
function_calls = [p["functionCall"] for p in parts if "functionCall" in p]
|
| 1536 |
usage = data.get("usageMetadata", {})
|
| 1537 |
prompt_tokens = usage.get("promptTokenCount", 0)
|
| 1538 |
completion_tokens = usage.get("candidatesTokenCount", 0)
|
|
|
|
| 1547 |
elapsed = time.time() - t0
|
| 1548 |
logger.info(f"Resposta gerada em {elapsed:.1f}s ({backend}, web={req.web})")
|
| 1549 |
|
| 1550 |
+
message = {"role": "assistant", "content": text}
|
| 1551 |
+
openai_tool_calls = None
|
| 1552 |
+
if 'tool_calls' in locals() and tool_calls:
|
| 1553 |
+
openai_tool_calls = tool_calls
|
| 1554 |
+
elif 'function_calls' in locals() and function_calls:
|
| 1555 |
+
openai_tool_calls = [
|
| 1556 |
+
{
|
| 1557 |
+
"id": f"call_{uuid.uuid4().hex[:8]}",
|
| 1558 |
+
"type": "function",
|
| 1559 |
+
"function": {
|
| 1560 |
+
"name": fc["name"],
|
| 1561 |
+
"arguments": json.dumps(fc.get("args", {})),
|
| 1562 |
+
},
|
| 1563 |
+
}
|
| 1564 |
+
for fc in function_calls
|
| 1565 |
+
]
|
| 1566 |
+
if openai_tool_calls:
|
| 1567 |
+
message["tool_calls"] = openai_tool_calls
|
| 1568 |
+
|
| 1569 |
return {
|
| 1570 |
"id": f"chatcmpl-{uuid.uuid4().hex[:8]}",
|
| 1571 |
"object": "chat.completion",
|
|
|
|
| 1574 |
"mode": req.mode,
|
| 1575 |
"choices": [{
|
| 1576 |
"index": 0,
|
| 1577 |
+
"message": message,
|
| 1578 |
+
"finish_reason": "tool_calls" if openai_tool_calls else (finish_reason or "stop"),
|
| 1579 |
}],
|
| 1580 |
"usage": {
|
| 1581 |
"prompt_tokens": prompt_tokens,
|