mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-06-21 23:02:46 +08:00
feat(translator): add test and logic to ensure object schemas include properties field
- Added `TestConvertClaudeRequestToOpenAI_ToolSchemaAddsMissingObjectProperties` to validate automatic addition of missing `properties` in `object` schemas. - Introduced `normalizeObjectSchemaProperties` to recursively ensure schemas of type `object` include an empty `properties` field if absent. - Updated `ConvertClaudeRequestToOpenAI` to apply schema normalization for improved compatibility with OpenAI schema expectations. Closes: #3165
This commit is contained in:
@@ -313,7 +313,7 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
|
||||
// Convert Anthropic input_schema to OpenAI function parameters
|
||||
if inputSchema := tool.Get("input_schema"); inputSchema.Exists() {
|
||||
openAIToolJSON, _ = sjson.SetBytes(openAIToolJSON, "function.parameters", inputSchema.Value())
|
||||
openAIToolJSON, _ = sjson.SetBytes(openAIToolJSON, "function.parameters", normalizeObjectSchemaProperties(inputSchema.Value()))
|
||||
}
|
||||
|
||||
toolsJSON, _ = sjson.SetRawBytes(toolsJSON, "-1", openAIToolJSON)
|
||||
@@ -352,6 +352,28 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeObjectSchemaProperties(schema any) any {
|
||||
switch value := schema.(type) {
|
||||
case map[string]any:
|
||||
if schemaType, ok := value["type"].(string); ok && schemaType == "object" {
|
||||
if _, ok := value["properties"]; !ok {
|
||||
value["properties"] = map[string]any{}
|
||||
}
|
||||
}
|
||||
for key, child := range value {
|
||||
value[key] = normalizeObjectSchemaProperties(child)
|
||||
}
|
||||
return value
|
||||
case []any:
|
||||
for i, child := range value {
|
||||
value[i] = normalizeObjectSchemaProperties(child)
|
||||
}
|
||||
return value
|
||||
default:
|
||||
return schema
|
||||
}
|
||||
}
|
||||
|
||||
func shouldMapClaudeThinkingToGPTReasoning(part gjson.Result) bool {
|
||||
signature := part.Get("signature")
|
||||
if !signature.Exists() || strings.TrimSpace(signature.String()) == "" {
|
||||
|
||||
@@ -496,6 +496,47 @@ func TestConvertClaudeRequestToOpenAI_SystemMessageScenarios(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertClaudeRequestToOpenAI_ToolSchemaAddsMissingObjectProperties(t *testing.T) {
|
||||
inputJSON := []byte(`{
|
||||
"model": "claude-3-opus",
|
||||
"tools": [
|
||||
{
|
||||
"name": "empty_params",
|
||||
"description": "No args",
|
||||
"input_schema": {"type": "object"}
|
||||
},
|
||||
{
|
||||
"name": "nested_params",
|
||||
"description": "Nested args",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nested": {"type": "object"},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {"type": "object"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"messages": [{"role": "user", "content": "hello"}]
|
||||
}`)
|
||||
|
||||
output := ConvertClaudeRequestToOpenAI("test-model", inputJSON, false)
|
||||
outputJSON := gjson.ParseBytes(output)
|
||||
|
||||
if got := outputJSON.Get("tools.0.function.parameters.properties"); !got.Exists() || !got.IsObject() {
|
||||
t.Fatalf("root object properties missing or invalid: %s", outputJSON.Get("tools.0.function.parameters").Raw)
|
||||
}
|
||||
if got := outputJSON.Get("tools.1.function.parameters.properties.nested.properties"); !got.Exists() || !got.IsObject() {
|
||||
t.Fatalf("nested object properties missing or invalid: %s", outputJSON.Get("tools.1.function.parameters").Raw)
|
||||
}
|
||||
if got := outputJSON.Get("tools.1.function.parameters.properties.items.items.properties"); !got.Exists() || !got.IsObject() {
|
||||
t.Fatalf("array item object properties missing or invalid: %s", outputJSON.Get("tools.1.function.parameters").Raw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertClaudeRequestToOpenAI_ToolResultOrderAndContent(t *testing.T) {
|
||||
inputJSON := `{
|
||||
"model": "claude-3-opus",
|
||||
|
||||
Reference in New Issue
Block a user