mirror of
https://github.com/xtekky/gpt4free.git
synced 2026-04-22 15:47:11 +08:00
feat: Implement JSON Schema sanitization for Gemini API compatibility and enhance OpenClaw setup script
This commit is contained in:
@@ -40,6 +40,32 @@ from ..helper import get_connector, get_system_prompt, format_media_prompt
|
||||
from ... import debug
|
||||
|
||||
|
||||
# JSON Schema keywords not supported by the Gemini API
|
||||
_UNSUPPORTED_SCHEMA_KEYS = {
|
||||
"patternProperties", "$schema", "$id", "$defs", "definitions",
|
||||
"if", "then", "else", "not", "allOf", "anyOf", "oneOf",
|
||||
"default", "examples", "readOnly", "writeOnly",
|
||||
"contentEncoding", "contentMediaType", "additionalProperties",
|
||||
}
|
||||
|
||||
|
||||
def _sanitize_schema(schema: dict) -> dict:
|
||||
"""Recursively remove JSON Schema keywords unsupported by the Gemini API."""
|
||||
if not isinstance(schema, dict):
|
||||
return schema
|
||||
result = {}
|
||||
for k, v in schema.items():
|
||||
if k in _UNSUPPORTED_SCHEMA_KEYS:
|
||||
continue
|
||||
if isinstance(v, dict):
|
||||
result[k] = _sanitize_schema(v)
|
||||
elif isinstance(v, list):
|
||||
result[k] = [_sanitize_schema(i) if isinstance(i, dict) else i for i in v]
|
||||
else:
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
|
||||
def get_antigravity_oauth_creds_path():
|
||||
"""Get the default path for Antigravity OAuth credentials."""
|
||||
return Path.home() / ".antigravity" / "oauth_creds.json"
|
||||
@@ -1079,7 +1105,7 @@ class AntigravityProvider:
|
||||
function_declarations.append({
|
||||
"name": func.get("name"),
|
||||
"description": func.get("description", ""),
|
||||
"parameters": func.get("parameters", {})
|
||||
"parameters": _sanitize_schema(func.get("parameters", {}))
|
||||
})
|
||||
if function_declarations:
|
||||
gemini_tools = [{"functionDeclarations": function_declarations}]
|
||||
|
||||
@@ -29,6 +29,32 @@ from ..helper import get_connector, get_system_prompt, format_media_prompt
|
||||
from ... import debug
|
||||
|
||||
|
||||
# JSON Schema keywords not supported by the Gemini API
|
||||
_UNSUPPORTED_SCHEMA_KEYS = {
|
||||
"patternProperties", "$schema", "$id", "$defs", "definitions",
|
||||
"if", "then", "else", "not", "allOf", "anyOf", "oneOf",
|
||||
"default", "examples", "readOnly", "writeOnly",
|
||||
"contentEncoding", "contentMediaType", "additionalProperties",
|
||||
}
|
||||
|
||||
|
||||
def _sanitize_schema(schema: dict) -> dict:
|
||||
"""Recursively remove JSON Schema keywords unsupported by the Gemini API."""
|
||||
if not isinstance(schema, dict):
|
||||
return schema
|
||||
result = {}
|
||||
for k, v in schema.items():
|
||||
if k in _UNSUPPORTED_SCHEMA_KEYS:
|
||||
continue
|
||||
if isinstance(v, dict):
|
||||
result[k] = _sanitize_schema(v)
|
||||
elif isinstance(v, list):
|
||||
result[k] = [_sanitize_schema(i) if isinstance(i, dict) else i for i in v]
|
||||
else:
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
|
||||
def get_oauth_creds_path():
|
||||
return Path.home() / ".gemini" / "oauth_creds.json"
|
||||
|
||||
@@ -674,7 +700,7 @@ class GeminiCLIProvider():
|
||||
function_declarations.append({
|
||||
"name": func.get("name"),
|
||||
"description": func.get("description", ""),
|
||||
"parameters": func.get("parameters", {})
|
||||
"parameters": _sanitize_schema(func.get("parameters", {}))
|
||||
})
|
||||
if function_declarations:
|
||||
gemini_tools = [{"functionDeclarations": function_declarations}]
|
||||
|
||||
@@ -298,6 +298,7 @@ class Api:
|
||||
details = exc.errors()
|
||||
modified_details = []
|
||||
for error in details:
|
||||
debug.log(f"Validation error: {error['loc']} - {error['msg']} ({error['type']})")
|
||||
modified_details.append({
|
||||
"loc": error["loc"],
|
||||
"message": error["msg"],
|
||||
|
||||
+13
-5
@@ -9,11 +9,19 @@ from typing import Optional
|
||||
@lru_cache(maxsize=1)
|
||||
def get_config_dir() -> Path:
|
||||
"""Get platform-appropriate config directory."""
|
||||
if sys.platform == "win32":
|
||||
return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
|
||||
elif sys.platform == "darwin":
|
||||
return Path.home() / "Library" / "Application Support"
|
||||
return Path.home() / ".config"
|
||||
def get_fallback_config_dir() -> Path:
|
||||
if sys.platform == "win32":
|
||||
return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
|
||||
elif sys.platform == "darwin":
|
||||
return Path.home() / "Library" / "Application Support"
|
||||
return Path.home() / ".config"
|
||||
config_dir = Path.home() / ".config"
|
||||
if not config_dir.exists():
|
||||
config_dir = get_fallback_config_dir()
|
||||
if not config_dir.exists():
|
||||
config_dir = Path.home() / ".g4f"
|
||||
config_dir.mkdir(parents=True, exist_ok=True)
|
||||
return config_dir
|
||||
|
||||
DEFAULT_PORT = 1337
|
||||
DEFAULT_TIMEOUT = 600
|
||||
|
||||
+15
-2
@@ -54,9 +54,20 @@ class ContentPart(TypedDict, total=False):
|
||||
bucket_id: str
|
||||
name: str
|
||||
|
||||
class Message(TypedDict):
|
||||
class ToolCallFunction(TypedDict, total=False):
|
||||
name: str
|
||||
arguments: str # JSON-encoded arguments string
|
||||
|
||||
class ToolCall(TypedDict, total=False):
|
||||
id: str
|
||||
type: str # e.g., "function"
|
||||
function: ToolCallFunction
|
||||
|
||||
class Message(TypedDict, total=False):
|
||||
role: str
|
||||
content: Union[str, List[ContentPart]]
|
||||
content: Optional[Union[str, List[ContentPart]]]
|
||||
tool_calls: Optional[List[ToolCall]]
|
||||
tool_call_id: str # present on "tool" role messages
|
||||
|
||||
Messages = List[Message]
|
||||
|
||||
@@ -86,6 +97,8 @@ __all__ = [
|
||||
"Messages",
|
||||
"Message",
|
||||
"ContentPart",
|
||||
"ToolCall",
|
||||
"ToolCallFunction",
|
||||
"Cookies",
|
||||
"PILImage", # Changed from "Image" to "PILImage" to match the actual class name
|
||||
"ImageType",
|
||||
|
||||
+12
-12
@@ -7,7 +7,7 @@
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "No API key provided. Proceeding without a Pollinations API key."
|
||||
echo "No API key provided. Proceeding without an API key."
|
||||
API_KEY=""
|
||||
else
|
||||
API_KEY="$1"
|
||||
@@ -46,7 +46,7 @@ EOF
|
||||
|
||||
cat > "${ENV_FILE}" <<EOF
|
||||
POLLINATIONS_API_KEY=${API_KEY}
|
||||
OPENAI_API_KEY=${API_KEY}
|
||||
OPENAI_API_KEY=
|
||||
GEMINI_API_KEY=
|
||||
EOF
|
||||
|
||||
@@ -86,11 +86,9 @@ if command -v openclaw >/dev/null 2>&1; then
|
||||
import json, sys, os
|
||||
|
||||
config_file = os.path.expanduser("~/.openclaw/openclaw.json")
|
||||
api_key = "${API_KEY}"
|
||||
|
||||
provider = {
|
||||
"baseUrl": "https://localhost:8080/v1",
|
||||
"apiKey": api_key,
|
||||
"baseUrl": "http://localhost:8080/v1",
|
||||
"api": "openai-completions",
|
||||
"models": [
|
||||
{
|
||||
@@ -115,13 +113,15 @@ except (FileNotFoundError, json.JSONDecodeError):
|
||||
|
||||
cfg.setdefault("models", {})["providers"] = cfg.get("models", {}).get("providers", {})
|
||||
cfg["models"]["providers"]["gpt4free"] = provider
|
||||
cfg.setdefault("tools", {}).setdefault("web", {})["search"] = {
|
||||
"provider": "perplexity",
|
||||
"perplexity": {
|
||||
"baseUrl": "https://g4f.dev/api/perplexity",
|
||||
"apiKey": "",
|
||||
"model": "turbo",
|
||||
},
|
||||
cfg["models"]["providers"]["g4f-perplexity"] = {
|
||||
"baseUrl": "https://perplexity.g4f-dev.workers.dev",
|
||||
"apiKey": "",
|
||||
"model": "turbo",
|
||||
};
|
||||
cfg["tools"] = cfg.get("tools", {})
|
||||
cfg["tools"]["web"] = cfg["tools"].get("web", {})
|
||||
cfg["tools"]["web"]["search"] = {
|
||||
"provider": "g4f-perplexity",
|
||||
}
|
||||
|
||||
with open(config_file, "w") as f:
|
||||
|
||||
Reference in New Issue
Block a user