返回提交历史
Modified
g4f/Provider/needs_auth/Antigravity.py
+27
-1
Modified
g4f/Provider/needs_auth/GeminiCLI.py
+27
-1
Modified
g4f/api/__init__.py
+1
-0
Modified
g4f/config.py
+13
-5
Modified
g4f/typing.py
+15
-2
Modified
scripts/setup-openclaw.sh
+12
-12
XFEstudio/gpt4free
feat: Implement JSON Schema sanitization for Gemini API compatibility and enhance OpenClaw setup script
71ed6243
代码差异
6 个文件
+95
-21
@@ -40,6 +40,32 @@ from ..helper import get_connector, get_system_prompt, format_media_prompt
40
40
from ... import debug
41
41
42
42
43
# JSON Schema keywords not supported by the Gemini API
44
_UNSUPPORTED_SCHEMA_KEYS = {
45
"patternProperties", "$schema", "$id", "$defs", "definitions",
46
"if", "then", "else", "not", "allOf", "anyOf", "oneOf",
47
"default", "examples", "readOnly", "writeOnly",
48
"contentEncoding", "contentMediaType", "additionalProperties",
49
}
50
51
52
def _sanitize_schema(schema: dict) -> dict:
53
"""Recursively remove JSON Schema keywords unsupported by the Gemini API."""
54
if not isinstance(schema, dict):
55
return schema
56
result = {}
57
for k, v in schema.items():
58
if k in _UNSUPPORTED_SCHEMA_KEYS:
59
continue
60
if isinstance(v, dict):
61
result[k] = _sanitize_schema(v)
62
elif isinstance(v, list):
63
result[k] = [_sanitize_schema(i) if isinstance(i, dict) else i for i in v]
64
else:
65
result[k] = v
66
return result
67
68
43
69
def get_antigravity_oauth_creds_path():
44
70
"""Get the default path for Antigravity OAuth credentials."""
45
71
return Path.home() / ".antigravity" / "oauth_creds.json"
@@ -1079,7 +1105,7 @@ class AntigravityProvider:
1079
1105
function_declarations.append({
1080
1106
"name": func.get("name"),
1081
1107
"description": func.get("description", ""),
1082
"parameters": func.get("parameters", {})
1108
"parameters": _sanitize_schema(func.get("parameters", {}))
1083
1109
})
1084
1110
if function_declarations:
1085
1111
gemini_tools = [{"functionDeclarations": function_declarations}]
@@ -29,6 +29,32 @@ from ..helper import get_connector, get_system_prompt, format_media_prompt
29
29
from ... import debug
30
30
31
31
32
# JSON Schema keywords not supported by the Gemini API
33
_UNSUPPORTED_SCHEMA_KEYS = {
34
"patternProperties", "$schema", "$id", "$defs", "definitions",
35
"if", "then", "else", "not", "allOf", "anyOf", "oneOf",
36
"default", "examples", "readOnly", "writeOnly",
37
"contentEncoding", "contentMediaType", "additionalProperties",
38
}
39
40
41
def _sanitize_schema(schema: dict) -> dict:
42
"""Recursively remove JSON Schema keywords unsupported by the Gemini API."""
43
if not isinstance(schema, dict):
44
return schema
45
result = {}
46
for k, v in schema.items():
47
if k in _UNSUPPORTED_SCHEMA_KEYS:
48
continue
49
if isinstance(v, dict):
50
result[k] = _sanitize_schema(v)
51
elif isinstance(v, list):
52
result[k] = [_sanitize_schema(i) if isinstance(i, dict) else i for i in v]
53
else:
54
result[k] = v
55
return result
56
57
32
58
def get_oauth_creds_path():
33
59
return Path.home() / ".gemini" / "oauth_creds.json"
34
60
@@ -674,7 +700,7 @@ class GeminiCLIProvider():
674
700
function_declarations.append({
675
701
"name": func.get("name"),
676
702
"description": func.get("description", ""),
677
"parameters": func.get("parameters", {})
703
"parameters": _sanitize_schema(func.get("parameters", {}))
678
704
})
679
705
if function_declarations:
680
706
gemini_tools = [{"functionDeclarations": function_declarations}]
@@ -298,6 +298,7 @@ class Api:
298
298
details = exc.errors()
299
299
modified_details = []
300
300
for error in details:
301
debug.log(f"Validation error: {error['loc']} - {error['msg']} ({error['type']})")
301
302
modified_details.append({
302
303
"loc": error["loc"],
303
304
"message": error["msg"],
@@ -9,11 +9,19 @@ from typing import Optional
9
9
@lru_cache(maxsize=1)
10
10
def get_config_dir() -> Path:
11
11
"""Get platform-appropriate config directory."""
12
if sys.platform == "win32":
13
return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
14
elif sys.platform == "darwin":
15
return Path.home() / "Library" / "Application Support"
16
return Path.home() / ".config"
12
def get_fallback_config_dir() -> Path:
13
if sys.platform == "win32":
14
return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
15
elif sys.platform == "darwin":
16
return Path.home() / "Library" / "Application Support"
17
return Path.home() / ".config"
18
config_dir = Path.home() / ".config"
19
if not config_dir.exists():
20
config_dir = get_fallback_config_dir()
21
if not config_dir.exists():
22
config_dir = Path.home() / ".g4f"
23
config_dir.mkdir(parents=True, exist_ok=True)
24
return config_dir
17
25
18
26
DEFAULT_PORT = 1337
19
27
DEFAULT_TIMEOUT = 600
@@ -54,9 +54,20 @@ class ContentPart(TypedDict, total=False):
54
54
bucket_id: str
55
55
name: str
56
56
57
class Message(TypedDict):
57
class ToolCallFunction(TypedDict, total=False):
58
name: str
59
arguments: str # JSON-encoded arguments string
60
61
class ToolCall(TypedDict, total=False):
62
id: str
63
type: str # e.g., "function"
64
function: ToolCallFunction
65
66
class Message(TypedDict, total=False):
58
67
role: str
59
content: Union[str, List[ContentPart]]
68
content: Optional[Union[str, List[ContentPart]]]
69
tool_calls: Optional[List[ToolCall]]
70
tool_call_id: str # present on "tool" role messages
60
71
61
72
Messages = List[Message]
62
73
@@ -86,6 +97,8 @@ __all__ = [
86
97
"Messages",
87
98
"Message",
88
99
"ContentPart",
100
"ToolCall",
101
"ToolCallFunction",
89
102
"Cookies",
90
103
"PILImage", # Changed from "Image" to "PILImage" to match the actual class name
91
104
"ImageType",
@@ -7,7 +7,7 @@
7
7
set -e
8
8
9
9
if [ -z "$1" ]; then
10
echo "No API key provided. Proceeding without a Pollinations API key."
10
echo "No API key provided. Proceeding without an API key."
11
11
API_KEY=""
12
12
else
13
13
API_KEY="$1"
@@ -46,7 +46,7 @@ EOF
46
46
47
47
cat > "${ENV_FILE}" <<EOF
48
48
POLLINATIONS_API_KEY=${API_KEY}
49
OPENAI_API_KEY=${API_KEY}
49
OPENAI_API_KEY=
50
50
GEMINI_API_KEY=
51
51
EOF
52
52
@@ -86,11 +86,9 @@ if command -v openclaw >/dev/null 2>&1; then
86
86
import json, sys, os
87
87
88
88
config_file = os.path.expanduser("~/.openclaw/openclaw.json")
89
api_key = "${API_KEY}"
90
89
91
90
provider = {
92
"baseUrl": "https://localhost:8080/v1",
93
"apiKey": api_key,
91
"baseUrl": "http://localhost:8080/v1",
94
92
"api": "openai-completions",
95
93
"models": [
96
94
{
@@ -115,13 +113,15 @@ except (FileNotFoundError, json.JSONDecodeError):
115
113
116
114
cfg.setdefault("models", {})["providers"] = cfg.get("models", {}).get("providers", {})
117
115
cfg["models"]["providers"]["gpt4free"] = provider
118
cfg.setdefault("tools", {}).setdefault("web", {})["search"] = {
119
"provider": "perplexity",
120
"perplexity": {
121
"baseUrl": "https://g4f.dev/api/perplexity",
122
"apiKey": "",
123
"model": "turbo",
124
},
116
cfg["models"]["providers"]["g4f-perplexity"] = {
117
"baseUrl": "https://perplexity.g4f-dev.workers.dev",
118
"apiKey": "",
119
"model": "turbo",
120
};
121
cfg["tools"] = cfg.get("tools", {})
122
cfg["tools"]["web"] = cfg["tools"].get("web", {})
123
cfg["tools"]["web"]["search"] = {
124
"provider": "g4f-perplexity",
125
125
}
126
126
127
127
with open(config_file, "w") as f: