From 204d41d6f3190e88adc85d0ef6c24462999266cb Mon Sep 17 00:00:00 2001 From: ripper Date: Wed, 9 Jul 2025 10:29:42 +0800 Subject: [PATCH] feat: add JSON Schema cleaning function to remove unsupported fields in Gemini API --- app/service/chat/gemini_chat_service.py | 37 ++++++++++++++++- app/service/chat/openai_chat_service.py | 41 ++++++++++++++++--- .../chat/vertex_express_chat_service.py | 37 ++++++++++++++++- 3 files changed, 107 insertions(+), 8 deletions(-) diff --git a/app/service/chat/gemini_chat_service.py b/app/service/chat/gemini_chat_service.py index 71ac3b3..ea37fe0 100644 --- a/app/service/chat/gemini_chat_service.py +++ b/app/service/chat/gemini_chat_service.py @@ -28,6 +28,33 @@ def _has_image_parts(contents: List[Dict[str, Any]]) -> bool: return False +def _clean_json_schema_properties(obj: Any) -> Any: + """清理JSON Schema中Gemini API不支持的字段""" + if not isinstance(obj, dict): + return obj + + # Gemini API不支持的JSON Schema字段 + unsupported_fields = { + "exclusiveMaximum", "exclusiveMinimum", "const", "examples", + "contentEncoding", "contentMediaType", "if", "then", "else", + "allOf", "anyOf", "oneOf", "not", "definitions", "$schema", + "$id", "$ref", "$comment", "readOnly", "writeOnly" + } + + cleaned = {} + for key, value in obj.items(): + if key in unsupported_fields: + continue + if isinstance(value, dict): + cleaned[key] = _clean_json_schema_properties(value) + elif isinstance(value, list): + cleaned[key] = [_clean_json_schema_properties(item) for item in value] + else: + cleaned[key] = value + + return cleaned + + def _build_tools(model: str, payload: Dict[str, Any]) -> List[Dict[str, Any]]: """构建工具""" @@ -40,7 +67,15 @@ def _build_tools(model: str, payload: Dict[str, Any]) -> List[Dict[str, Any]]: for k, v in item.items(): if k == "functionDeclarations" and v and isinstance(v, list): functions = record.get("functionDeclarations", []) - functions.extend(v) + # 清理每个函数声明中的不支持字段 + cleaned_functions = [] + for func in v: + if isinstance(func, dict): + cleaned_func = _clean_json_schema_properties(func) + cleaned_functions.append(cleaned_func) + else: + cleaned_functions.append(func) + functions.extend(cleaned_functions) record["functionDeclarations"] = functions else: record[k] = v diff --git a/app/service/chat/openai_chat_service.py b/app/service/chat/openai_chat_service.py index d2866a0..432b8b4 100644 --- a/app/service/chat/openai_chat_service.py +++ b/app/service/chat/openai_chat_service.py @@ -26,16 +26,43 @@ from app.service.key.key_manager import KeyManager logger = get_openai_logger() -def _has_media_parts(contents: List[Dict[str, Any]]) -> bool: - """判断消息是否包含图片、音频或视频部分 (inline_data)""" - for content in contents: - if content and "parts" in content and isinstance(content["parts"], list): - for part in content["parts"]: - if isinstance(part, dict) and "inline_data" in part: +def _has_media_parts(messages: List[Dict[str, Any]]) -> bool: + """判断消息是否包含多媒体部分""" + for message in messages: + if "parts" in message: + for part in message["parts"]: + if "image_url" in part or "inline_data" in part: return True return False +def _clean_json_schema_properties(obj: Any) -> Any: + """清理JSON Schema中Gemini API不支持的字段""" + if not isinstance(obj, dict): + return obj + + # Gemini API不支持的JSON Schema字段 + unsupported_fields = { + "exclusiveMaximum", "exclusiveMinimum", "const", "examples", + "contentEncoding", "contentMediaType", "if", "then", "else", + "allOf", "anyOf", "oneOf", "not", "definitions", "$schema", + "$id", "$ref", "$comment", "readOnly", "writeOnly" + } + + cleaned = {} + for key, value in obj.items(): + if key in unsupported_fields: + continue + if isinstance(value, dict): + cleaned[key] = _clean_json_schema_properties(value) + elif isinstance(value, list): + cleaned[key] = [_clean_json_schema_properties(item) for item in value] + else: + cleaned[key] = value + + return cleaned + + def _build_tools( request: ChatRequest, messages: List[Dict[str, Any]] ) -> List[Dict[str, Any]]: @@ -76,6 +103,8 @@ def _build_tools( ): function.pop("parameters", None) + # 清理函数中的不支持字段 + function = _clean_json_schema_properties(function) function_declarations.append(function) if function_declarations: diff --git a/app/service/chat/vertex_express_chat_service.py b/app/service/chat/vertex_express_chat_service.py index 313cb89..6dcfc08 100644 --- a/app/service/chat/vertex_express_chat_service.py +++ b/app/service/chat/vertex_express_chat_service.py @@ -28,6 +28,33 @@ def _has_image_parts(contents: List[Dict[str, Any]]) -> bool: return False +def _clean_json_schema_properties(obj: Any) -> Any: + """清理JSON Schema中Gemini API不支持的字段""" + if not isinstance(obj, dict): + return obj + + # Gemini API不支持的JSON Schema字段 + unsupported_fields = { + "exclusiveMaximum", "exclusiveMinimum", "const", "examples", + "contentEncoding", "contentMediaType", "if", "then", "else", + "allOf", "anyOf", "oneOf", "not", "definitions", "$schema", + "$id", "$ref", "$comment", "readOnly", "writeOnly" + } + + cleaned = {} + for key, value in obj.items(): + if key in unsupported_fields: + continue + if isinstance(value, dict): + cleaned[key] = _clean_json_schema_properties(value) + elif isinstance(value, list): + cleaned[key] = [_clean_json_schema_properties(item) for item in value] + else: + cleaned[key] = value + + return cleaned + + def _build_tools(model: str, payload: Dict[str, Any]) -> List[Dict[str, Any]]: """构建工具""" @@ -40,7 +67,15 @@ def _build_tools(model: str, payload: Dict[str, Any]) -> List[Dict[str, Any]]: for k, v in item.items(): if k == "functionDeclarations" and v and isinstance(v, list): functions = record.get("functionDeclarations", []) - functions.extend(v) + # 清理每个函数声明中的不支持字段 + cleaned_functions = [] + for func in v: + if isinstance(func, dict): + cleaned_func = _clean_json_schema_properties(func) + cleaned_functions.append(cleaned_func) + else: + cleaned_functions.append(func) + functions.extend(cleaned_functions) record["functionDeclarations"] = functions else: record[k] = v