From 5a64ae2a290b98a8e425265b3cfe6d587340fab2 Mon Sep 17 00:00:00 2001 From: Seefs Date: Sun, 21 Dec 2025 17:09:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=A8=A1=E5=9E=8B=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=92=88=E5=AF=B9Vertex=E6=B8=A0=E9=81=93?= =?UTF-8?q?=E8=BF=87=E6=BB=A4content[].part[].functionResponse.id=E7=9A=84?= =?UTF-8?q?=E9=80=89=E9=A1=B9=EF=BC=8C=E9=BB=98=E8=AE=A4=E5=90=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + relay/common/relay_info.go | 45 +++++++++++++++++++ relay/gemini_handler.go | 5 +++ setting/model_setting/gemini.go | 4 +- web/src/components/settings/ModelSetting.jsx | 2 + web/src/i18n/locales/en.json | 2 + web/src/i18n/locales/fr.json | 2 + web/src/i18n/locales/ja.json | 2 + web/src/i18n/locales/ru.json | 2 + web/src/i18n/locales/vi.json | 2 + web/src/i18n/locales/zh.json | 2 + .../Setting/Model/SettingGeminiModel.jsx | 18 ++++++++ 12 files changed, 86 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c3cde5d39..640e5ec6a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ new-api tiktoken_cache .eslintcache .gocache +.gomodcache/ .cache web/bun.lock diff --git a/relay/common/relay_info.go b/relay/common/relay_info.go index 40f79463f..1b9762fec 100644 --- a/relay/common/relay_info.go +++ b/relay/common/relay_info.go @@ -11,6 +11,7 @@ import ( "github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/dto" relayconstant "github.com/QuantumNous/new-api/relay/constant" + "github.com/QuantumNous/new-api/setting/model_setting" "github.com/QuantumNous/new-api/types" "github.com/gin-gonic/gin" @@ -634,3 +635,47 @@ func RemoveDisabledFields(jsonData []byte, channelOtherSettings dto.ChannelOther } return jsonDataAfter, nil } + +// RemoveGeminiDisabledFields removes disabled fields from Gemini request JSON data +// Currently supports removing functionResponse.id field which Vertex AI does not support +func RemoveGeminiDisabledFields(jsonData []byte) ([]byte, error) { + if !model_setting.GetGeminiSettings().RemoveFunctionResponseIdEnabled { + return jsonData, nil + } + + var data map[string]interface{} + if err := common.Unmarshal(jsonData, &data); err != nil { + common.SysError("RemoveGeminiDisabledFields Unmarshal error: " + err.Error()) + return jsonData, nil + } + + // Process contents array + // Handle both camelCase (functionResponse) and snake_case (function_response) + if contents, ok := data["contents"].([]interface{}); ok { + for _, content := range contents { + if contentMap, ok := content.(map[string]interface{}); ok { + if parts, ok := contentMap["parts"].([]interface{}); ok { + for _, part := range parts { + if partMap, ok := part.(map[string]interface{}); ok { + // Check functionResponse (camelCase) + if funcResp, ok := partMap["functionResponse"].(map[string]interface{}); ok { + delete(funcResp, "id") + } + // Check function_response (snake_case) + if funcResp, ok := partMap["function_response"].(map[string]interface{}); ok { + delete(funcResp, "id") + } + } + } + } + } + } + } + + jsonDataAfter, err := common.Marshal(data) + if err != nil { + common.SysError("RemoveGeminiDisabledFields Marshal error: " + err.Error()) + return jsonData, nil + } + return jsonDataAfter, nil +} diff --git a/relay/gemini_handler.go b/relay/gemini_handler.go index af13341bf..6041b765a 100644 --- a/relay/gemini_handler.go +++ b/relay/gemini_handler.go @@ -162,6 +162,11 @@ func GeminiHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ } } + // remove disabled fields for Vertex AI + if info.ChannelType == constant.ChannelTypeVertexAi { + jsonData, _ = relaycommon.RemoveGeminiDisabledFields(jsonData) + } + logger.LogDebug(c, "Gemini request body: "+string(jsonData)) requestBody = bytes.NewReader(jsonData) diff --git a/setting/model_setting/gemini.go b/setting/model_setting/gemini.go index 55f721e95..30d56e345 100644 --- a/setting/model_setting/gemini.go +++ b/setting/model_setting/gemini.go @@ -4,7 +4,7 @@ import ( "github.com/QuantumNous/new-api/setting/config" ) -// GeminiSettings 定义Gemini模型的配置 +// GeminiSettings defines Gemini model configuration. 注意bool要以enabled结尾才可以生效编辑 type GeminiSettings struct { SafetySettings map[string]string `json:"safety_settings"` VersionSettings map[string]string `json:"version_settings"` @@ -12,6 +12,7 @@ type GeminiSettings struct { ThinkingAdapterEnabled bool `json:"thinking_adapter_enabled"` ThinkingAdapterBudgetTokensPercentage float64 `json:"thinking_adapter_budget_tokens_percentage"` FunctionCallThoughtSignatureEnabled bool `json:"function_call_thought_signature_enabled"` + RemoveFunctionResponseIdEnabled bool `json:"remove_function_response_id_enabled"` } // 默认配置 @@ -30,6 +31,7 @@ var defaultGeminiSettings = GeminiSettings{ ThinkingAdapterEnabled: false, ThinkingAdapterBudgetTokensPercentage: 0.6, FunctionCallThoughtSignatureEnabled: true, + RemoveFunctionResponseIdEnabled: true, } // 全局实例 diff --git a/web/src/components/settings/ModelSetting.jsx b/web/src/components/settings/ModelSetting.jsx index 768e10709..d498a3212 100644 --- a/web/src/components/settings/ModelSetting.jsx +++ b/web/src/components/settings/ModelSetting.jsx @@ -32,6 +32,7 @@ const ModelSetting = () => { 'gemini.safety_settings': '', 'gemini.version_settings': '', 'gemini.supported_imagine_models': '', + 'gemini.remove_function_response_id_enabled': true, 'claude.model_headers_settings': '', 'claude.thinking_adapter_enabled': true, 'claude.default_max_tokens': '', @@ -64,6 +65,7 @@ const ModelSetting = () => { item.value = JSON.stringify(JSON.parse(item.value), null, 2); } } + // Keep boolean config keys ending with enabled/Enabled so UI parses correctly. if (item.key.endsWith('Enabled') || item.key.endsWith('enabled')) { newInputs[item.key] = toBoolean(item.value); } else { diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 188f9e693..4de684048 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -153,6 +153,7 @@ "URL链接": "URL Link", "USD (美元)": "USD (US Dollar)", "User Info Endpoint": "User Info Endpoint", + "Vertex AI 不支持 functionResponse.id 字段,开启后将自动移除该字段": "Vertex AI does not support the functionResponse.id field. When enabled, this field will be automatically removed", "Webhook 密钥": "Webhook Secret", "Webhook 签名密钥": "Webhook Signature Key", "Webhook地址": "Webhook URL", @@ -1510,6 +1511,7 @@ "私有IP访问详细说明": "⚠️ Security Warning: Enabling this allows access to internal network resources (localhost, private networks). Only enable if you need to access internal services and understand the security implications.", "私有部署地址": "Private Deployment Address", "秒": "Second", + "移除 functionResponse.id 字段": "Remove functionResponse.id Field", "移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目": "Removal of One API copyright mark must first be authorized. Project maintenance requires a lot of effort. If this project is meaningful to you, please actively support it.", "窗口处理": "window handling", "窗口等待": "window wait", diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index b314f8608..d05cdf569 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -154,6 +154,7 @@ "URL链接": "Lien URL", "USD (美元)": "USD (Dollar US)", "User Info Endpoint": "Point de terminaison des informations utilisateur", + "Vertex AI 不支持 functionResponse.id 字段,开启后将自动移除该字段": "Vertex AI ne prend pas en charge le champ functionResponse.id. Lorsqu'il est activé, ce champ sera automatiquement supprimé", "Webhook 密钥": "Clé Webhook", "Webhook 签名密钥": "Clé de signature Webhook", "Webhook地址": "URL du Webhook", @@ -1520,6 +1521,7 @@ "私有IP访问详细说明": "⚠️ Avertissement de sécurité : l'activation de cette option autorise l'accès aux ressources du réseau interne (localhost, réseaux privés). N'activez cette option que si vous devez accéder à des services internes et que vous comprenez les implications en matière de sécurité.", "私有部署地址": "Adresse de déploiement privée", "秒": "Seconde", + "移除 functionResponse.id 字段": "Supprimer le champ functionResponse.id", "移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目": "La suppression de la marque de copyright de One API doit d'abord être autorisée. La maintenance du projet demande beaucoup d'efforts. Si ce projet a du sens pour vous, veuillez le soutenir activement.", "窗口处理": "gestion des fenêtres", "窗口等待": "attente de la fenêtre", diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json index b5767f662..2b3ea9f02 100644 --- a/web/src/i18n/locales/ja.json +++ b/web/src/i18n/locales/ja.json @@ -136,6 +136,7 @@ "Uptime Kuma监控分类管理,可以配置多个监控分类用于服务状态展示(最多20个)": "Uptime Kumaの監視分類管理:サービスステータス表示用に、複数の監視分類を設定できます(最大20個)", "URL链接": "URL", "User Info Endpoint": "User Info Endpoint", + "Vertex AI 不支持 functionResponse.id 字段,开启后将自动移除该字段": "Vertex AIはfunctionResponse.idフィールドをサポートしていません。有効にすると、このフィールドは自動的に削除されます", "Webhook 签名密钥": "Webhook署名シークレット", "Webhook地址": "Webhook URL", "Webhook地址必须以https://开头": "Webhook URLは、https://で始まることが必須です", @@ -1440,6 +1441,7 @@ "私有IP访问详细说明": "プライベートIPアクセスの詳細説明", "私有部署地址": "プライベートデプロイ先URL", "秒": "秒", + "移除 functionResponse.id 字段": "functionResponse.idフィールドを削除", "移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目": "One APIの著作権表示を削除するには、事前の許可が必要です。プロジェクトの維持には多大な労力がかかります。もしこのプロジェクトがあなたにとって有意義でしたら、積極的なご支援をお願いいたします", "窗口处理": "ウィンドウ処理", "窗口等待": "ウィンドウ待機中", diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index 046a84bff..76616cbdb 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -156,6 +156,7 @@ "URL链接": "URL ссылка", "USD (美元)": "USD (доллар США)", "User Info Endpoint": "Конечная точка информации о пользователе", + "Vertex AI 不支持 functionResponse.id 字段,开启后将自动移除该字段": "Vertex AI не поддерживает поле functionResponse.id. При включении это поле будет автоматически удалено", "Webhook 密钥": "Секрет вебхука", "Webhook 签名密钥": "Ключ подписи Webhook", "Webhook地址": "Адрес Webhook", @@ -1531,6 +1532,7 @@ "私有IP访问详细说明": "⚠️ Предупреждение безопасности: включение этой опции позволит доступ к ресурсам внутренней сети (localhost, частные сети). Включайте только при необходимости доступа к внутренним службам и понимании рисков безопасности.", "私有部署地址": "Адрес частного развёртывания", "秒": "секунда", + "移除 functionResponse.id 字段": "Удалить поле functionResponse.id", "移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目": "Удаление авторских знаков One API требует предварительного разрешения, поддержка проекта требует больших усилий, если этот проект важен для вас, пожалуйста, поддержите его", "窗口处理": "Обработка окна", "窗口等待": "Ожидание окна", diff --git a/web/src/i18n/locales/vi.json b/web/src/i18n/locales/vi.json index 669cafec6..556501da2 100644 --- a/web/src/i18n/locales/vi.json +++ b/web/src/i18n/locales/vi.json @@ -136,6 +136,7 @@ "Uptime Kuma监控分类管理,可以配置多个监控分类用于服务状态展示(最多20个)": "Quản lý danh mục giám sát Uptime Kuma, bạn có thể cấu hình nhiều danh mục giám sát để hiển thị trạng thái dịch vụ (tối đa 20)", "URL链接": "Liên kết URL", "User Info Endpoint": "User Info Endpoint", + "Vertex AI 不支持 functionResponse.id 字段,开启后将自动移除该字段": "Vertex AI không hỗ trợ trường functionResponse.id. Khi bật, trường này sẽ tự động bị xóa", "Webhook 签名密钥": "Khóa chữ ký Webhook", "Webhook地址": "URL Webhook", "Webhook地址必须以https://开头": "URL Webhook phải bắt đầu bằng https://", @@ -2648,6 +2649,7 @@ "私有IP访问详细说明": "⚠️ Cảnh báo bảo mật: Bật tính năng này cho phép truy cập vào tài nguyên mạng nội bộ (localhost, mạng riêng). Chỉ bật nếu bạn cần truy cập các dịch vụ nội bộ và hiểu rõ các rủi ro bảo mật.", "私有部署地址": "Địa chỉ triển khai riêng", "秒": "Giây", + "移除 functionResponse.id 字段": "Xóa trường functionResponse.id", "移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目": "Việc xóa dấu bản quyền One API trước tiên phải được ủy quyền. Việc bảo trì dự án đòi hỏi rất nhiều nỗ lực. Nếu dự án này có ý nghĩa với bạn, vui lòng chủ động ủng hộ dự án này.", "窗口处理": "xử lý cửa sổ", "窗口等待": "chờ cửa sổ", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index 304a13b03..a8d28acca 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -150,6 +150,7 @@ "URL链接": "URL链接", "USD (美元)": "USD (美元)", "User Info Endpoint": "User Info Endpoint", + "Vertex AI 不支持 functionResponse.id 字段,开启后将自动移除该字段": "Vertex AI 不支持 functionResponse.id 字段,开启后将自动移除该字段", "Webhook 密钥": "Webhook 密钥", "Webhook 签名密钥": "Webhook 签名密钥", "Webhook地址": "Webhook地址", @@ -1498,6 +1499,7 @@ "私有IP访问详细说明": "⚠️ 安全警告:启用此选项将允许访问内网资源(本地主机、私有网络)。仅在需要访问内部服务且了解安全风险的情况下启用。", "私有部署地址": "私有部署地址", "秒": "秒", + "移除 functionResponse.id 字段": "移除 functionResponse.id 字段", "移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目": "移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目", "窗口处理": "窗口处理", "窗口等待": "窗口等待", diff --git a/web/src/pages/Setting/Model/SettingGeminiModel.jsx b/web/src/pages/Setting/Model/SettingGeminiModel.jsx index e75a4ca91..75b0f0242 100644 --- a/web/src/pages/Setting/Model/SettingGeminiModel.jsx +++ b/web/src/pages/Setting/Model/SettingGeminiModel.jsx @@ -46,6 +46,7 @@ const DEFAULT_GEMINI_INPUTS = { 'gemini.thinking_adapter_enabled': false, 'gemini.thinking_adapter_budget_tokens_percentage': 0.6, 'gemini.function_call_thought_signature_enabled': true, + 'gemini.remove_function_response_id_enabled': true, }; export default function SettingGeminiModel(props) { @@ -186,6 +187,23 @@ export default function SettingGeminiModel(props) { /> + + + + setInputs({ + ...inputs, + 'gemini.remove_function_response_id_enabled': value, + }) + } + /> + +