Wilker commited on
Commit
dbb524c
·
1 Parent(s): 5ccd423

fix(app): restaura suporte a tool calling sem SyntaxError

Browse files
Files changed (1) hide show
  1. app.py +85 -10
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
- text = data["choices"][0]["message"]["content"]
 
 
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
- text = data["choices"][0]["message"]["content"]
 
 
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
- text = data["choices"][0]["message"]["content"]
 
 
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": {"role": "assistant", "content": text},
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
- text = data["choices"][0]["message"]["content"]
 
 
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
- text = data["choices"][0]["message"]["content"]
 
 
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
- text = data["candidates"][0]["content"]["parts"][0]["text"]
 
 
 
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": {"role": "assistant", "content": text},
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,