返回提交历史
Modified
g4f/Provider/needs_auth/Antigravity.py
+34
-33
Modified
g4f/Provider/needs_auth/GeminiCLI.py
+22
-19
XFEstudio/gpt4free
fix: Improve tool response handling and thought signature restoration in Antigravity and GeminiCLI providers
f41007cc
代码差异
2 个文件
+56
-52
@@ -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)
@@ -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):