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

XFEstudio/gpt4free

feat: Implement JSON Schema sanitization for Gemini API compatibility and enhance OpenClaw setup script

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

代码差异

6 个文件 +95 -21
Modified g4f/Provider/needs_auth/Antigravity.py +27 -1
@@ -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}]
Modified g4f/Provider/needs_auth/GeminiCLI.py +27 -1
@@ -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}]
Modified g4f/api/__init__.py +1 -0
@@ -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"],
Modified g4f/config.py +13 -5
@@ -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
Modified g4f/typing.py +15 -2
@@ -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",
Modified scripts/setup-openclaw.sh +12 -12
@@ -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: