XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/gpt4free

fix: Improve tool response handling and thought signature restoration in Antigravity and GeminiCLI providers

f41007cc
hlohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

2 个文件 +56 -52
Modified g4f/Provider/needs_auth/Antigravity.py +34 -33
@@ -955,35 +955,42 @@ class AntigravityProvider:
955 955 "Could not discover project ID. Ensure authentication or set ANTIGRAVITY_PROJECT_ID."
956 956 )
957 957
958
958 959 @staticmethod
959 960 def _messages_to_gemini_format(messages: list, media: MediaListType) -> List[Dict[str, Any]]:
960 """Convert OpenAI-style messages to Gemini format."""
961 961 format_messages = []
962 962 for msg in messages:
963 # Convert a ChatMessage dict to GeminiFormattedMessage dict
963 964 role = "model" if msg["role"] == "assistant" else "user"
964 965
965 content = msg.get("content")
966
967 966 # Handle tool role (OpenAI style)
967 # Group consecutive tool responses into a single user turn so that
968 # the number of functionResponse parts equals the number of functionCall parts.
968 969 if msg["role"] == "tool":
969 parts = [
970 {
971 "functionResponse": {
972 "name": msg.get("tool_call_id", "unknown_function"),
973 "response": {
974 "result": (
975 content
976 if isinstance(content, str)
977 else json.dumps(content)
978 )
979 },
980 }
970 tool_result = msg.get("content", "")
971 func_response_part = {
972 "functionResponse": {
973 "name": msg.get("tool_call_id", "unknown_function"),
974 "response": {
975 "result": (
976 tool_result
977 if isinstance(tool_result, str)
978 else json.dumps(tool_result)
979 )
980 },
981 981 }
982 ]
982 }
983 if (format_messages and format_messages[-1]["role"] == "user"
984 and any("functionResponse" in p for p in format_messages[-1]["parts"])):
985 format_messages[-1]["parts"].append(func_response_part)
986 else:
987 format_messages.append({"role": "user", "parts": [func_response_part]})
988 continue
983 989
984 990 # Handle assistant messages with tool calls
985 991 elif msg["role"] == "assistant" and msg.get("tool_calls"):
986 992 parts = []
993 content = msg.get("content")
987 994 if isinstance(content, str) and content.strip():
988 995 parts.append({"text": content})
989 996 for tool_call in msg["tool_calls"]:
@@ -994,24 +1001,21 @@ class AntigravityProvider:
994 1001 }
995 1002 # Restore thought_signature for Gemini thinking models when available
996 1003 thought_sig = tool_call.get("extra_content", {}).get("google", {}).get("thought_signature", "skip_thought_signature_validator")
997 if idx == 0: # Only add skip_thought_signature_validator for the first tool call if no signature is present
998 parts.append({"functionCall": func_call, "thoughtSignature": thought_sig})
999 else:
1000 parts.append({"functionCall": func_call})
1004 parts.append({"functionCall": func_call, "thoughtSignature": thought_sig})
1001 1005
1002 1006 # Handle string content
1003 elif isinstance(content, str):
1004 parts = [{"text": content}]
1007 elif isinstance(msg["content"], str):
1008 parts = [{"text": msg["content"]}]
1005 1009
1006 1010 # Handle array content (possibly multimodal)
1007 elif isinstance(content, list):
1011 elif isinstance(msg["content"], list):
1008 1012 parts = []
1009 for item in content:
1010 ctype = item.get("type")
1013 for content in msg["content"]:
1014 ctype = content.get("type")
1011 1015 if ctype == "text":
1012 parts.append({"text": item["text"]})
1016 parts.append({"text": content["text"]})
1013 1017 elif ctype == "image_url":
1014 image_url = item.get("image_url", {}).get("url")
1018 image_url = content.get("image_url", {}).get("url")
1015 1019 if not image_url:
1016 1020 continue
1017 1021 if image_url.startswith("data:"):
@@ -1023,7 +1027,7 @@ class AntigravityProvider:
1023 1027 parts.append(
1024 1028 {
1025 1029 "fileData": {
1026 "mimeType": "image/jpeg",
1030 "mimeType": "image/jpeg", # Could improve by validation
1027 1031 "fileUri": image_url,
1028 1032 }
1029 1033 }
@@ -1034,8 +1038,6 @@ class AntigravityProvider:
1034 1038 parts = []
1035 1039
1036 1040 format_messages.append({"role": role, "parts": parts})
1037
1038 # Handle media attachments
1039 1041 if media:
1040 1042 if not format_messages:
1041 1043 format_messages.append({"role": "user", "parts": []})
@@ -1048,7 +1050,7 @@ class AntigravityProvider:
1048 1050 {
1049 1051 "fileData": {
1050 1052 "mimeType": f"image/{extension}",
1051 "fileUri": media_data,
1053 "fileUri": image_url,
1052 1054 }
1053 1055 }
1054 1056 )
@@ -1060,7 +1062,6 @@ class AntigravityProvider:
1060 1062 "data": base64.b64encode(media_data).decode()
1061 1063 }
1062 1064 })
1063
1064 1065 return format_messages
1065 1066
1066 1067 async def stream_content(
@@ -1291,7 +1292,7 @@ class AntigravityProvider:
1291 1292 if "thoughtSignature" in part:
1292 1293 tool_call_obj["extra_content"] = {
1293 1294 "google": {
1294 "thought_signature": tc["thought_signature"]
1295 "thought_signature": part["thoughtSignature"]
1295 1296 }
1296 1297 }
1297 1298 openai_tool_calls.append(tool_call_obj)
Modified g4f/Provider/needs_auth/GeminiCLI.py +22 -19
@@ -571,22 +571,28 @@ class GeminiCLIProvider():
571 571 role = "model" if msg["role"] == "assistant" else "user"
572 572
573 573 # Handle tool role (OpenAI style)
574 # Group consecutive tool responses into a single user turn so that
575 # the number of functionResponse parts equals the number of functionCall parts.
574 576 if msg["role"] == "tool":
575 577 tool_result = msg.get("content", "")
576 parts = [
577 {
578 "functionResponse": {
579 "name": msg.get("tool_call_id", "unknown_function"),
580 "response": {
581 "result": (
582 tool_result
583 if isinstance(tool_result, str)
584 else json.dumps(tool_result)
585 )
586 },
587 }
578 func_response_part = {
579 "functionResponse": {
580 "name": msg.get("tool_call_id", "unknown_function"),
581 "response": {
582 "result": (
583 tool_result
584 if isinstance(tool_result, str)
585 else json.dumps(tool_result)
586 )
587 },
588 588 }
589 ]
589 }
590 if (format_messages and format_messages[-1]["role"] == "user"
591 and any("functionResponse" in p for p in format_messages[-1]["parts"])):
592 format_messages[-1]["parts"].append(func_response_part)
593 else:
594 format_messages.append({"role": "user", "parts": [func_response_part]})
595 continue
590 596
591 597 # Handle assistant messages with tool calls
592 598 elif msg["role"] == "assistant" and msg.get("tool_calls"):
@@ -594,18 +600,15 @@ class GeminiCLIProvider():
594 600 content = msg.get("content")
595 601 if isinstance(content, str) and content.strip():
596 602 parts.append({"text": content})
597 for idx, tool_call in enumerate(msg["tool_calls"]):
603 for tool_call in msg["tool_calls"]:
598 604 if tool_call.get("type") == "function":
599 605 func_call = {
600 606 "name": tool_call["function"]["name"],
601 607 "args": json.loads(tool_call["function"]["arguments"]),
602 608 }
603 # Restore thought_signature required by Gemini thinking models
609 # Restore thought_signature for Gemini thinking models when available
604 610 thought_sig = tool_call.get("extra_content", {}).get("google", {}).get("thought_signature", "skip_thought_signature_validator")
605 if idx == 0: # Only add skip_thought_signature_validator for the first tool call if no signature is present
606 parts.append({"functionCall": func_call, "thoughtSignature": thought_sig})
607 else:
608 parts.append({"functionCall": func_call})
611 parts.append({"functionCall": func_call, "thoughtSignature": thought_sig})
609 612
610 613 # Handle string content
611 614 elif isinstance(msg["content"], str):