diff --git a/backend/internal/service/gemini_messages_compat_service.go b/backend/internal/service/gemini_messages_compat_service.go index 82cf9a20f3..5f9854167d 100644 --- a/backend/internal/service/gemini_messages_compat_service.go +++ b/backend/internal/service/gemini_messages_compat_service.go @@ -3587,12 +3587,20 @@ func cleanToolSchema(schema any) any { if key == "$schema" || key == "$id" || key == "$ref" || key == "$defs" || key == "definitions" || key == "additionalProperties" || key == "patternProperties" || key == "minLength" || - key == "maxLength" || key == "minItems" || key == "maxItems" || key == "exclusiveMinimum" { + key == "maxLength" || key == "minItems" || key == "maxItems" || key == "exclusiveMinimum" || + key == "deprecated" { continue } // 递归清理嵌套对象 cleaned[key] = cleanToolSchema(value) } + if enum, ok := cleaned["enum"].([]any); ok { + if normalized, ok := normalizeGeminiEnum(enum); ok { + cleaned["enum"] = normalized + } else { + delete(cleaned, "enum") + } + } // 规范化 type 字段为大写 if typeVal, ok := cleaned["type"].(string); ok { cleaned["type"] = strings.ToUpper(typeVal) @@ -3627,6 +3635,28 @@ func cleanToolSchema(schema any) any { } } +func normalizeGeminiEnum(values []any) ([]any, bool) { + normalized := make([]any, len(values)) + for i, value := range values { + if stringValue, ok := value.(string); ok { + normalized[i] = stringValue + continue + } + + switch value.(type) { + case nil, bool, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, json.Number: + encoded, err := json.Marshal(value) + if err != nil { + return nil, false + } + normalized[i] = string(encoded) + default: + return nil, false + } + } + return normalized, true +} + func incrementIntegralSchemaBound(value any) (any, bool) { switch v := value.(type) { case float64: diff --git a/backend/internal/service/gemini_messages_compat_service_test.go b/backend/internal/service/gemini_messages_compat_service_test.go index 2023edc979..e9ee74e3be 100644 --- a/backend/internal/service/gemini_messages_compat_service_test.go +++ b/backend/internal/service/gemini_messages_compat_service_test.go @@ -471,6 +471,44 @@ func TestCleanToolSchema_DropsAmbiguousExclusiveMinimumWithoutConversion(t *test } } +func TestCleanToolSchema_RemovesNestedDeprecatedAndNormalizesMixedScalarEnum(t *testing.T) { + schema := map[string]any{ + "anyOf": []any{ + map[string]any{ + "type": "string", + "deprecated": true, + }, + map[string]any{ + "enum": []any{"enabled", false, float64(1), nil}, + }, + }, + } + + cleaned, ok := cleanToolSchema(schema).(map[string]any) + require.True(t, ok) + anyOf, ok := cleaned["anyOf"].([]any) + require.True(t, ok) + require.Len(t, anyOf, 2) + + deprecatedSchema, ok := anyOf[0].(map[string]any) + require.True(t, ok) + require.NotContains(t, deprecatedSchema, "deprecated") + + enumSchema, ok := anyOf[1].(map[string]any) + require.True(t, ok) + require.Equal(t, []any{"enabled", "false", "1", "null"}, enumSchema["enum"]) +} + +func TestCleanToolSchema_DropsEnumWithNonScalarValue(t *testing.T) { + schema := map[string]any{ + "enum": []any{"valid", map[string]any{"invalid": true}}, + } + + cleaned, ok := cleanToolSchema(schema).(map[string]any) + require.True(t, ok) + require.NotContains(t, cleaned, "enum") +} + func TestConvertClaudeToolsToGeminiTools_PreservesWebSearchAlongsideFunctions(t *testing.T) { tools := []any{ map[string]any{