diff --git a/relaykit/relayconvert/internal/oai_chat/to_claude_messages_req.go b/relaykit/relayconvert/internal/oai_chat/to_claude_messages_req.go index fec9d95e..ee4722f5 100644 --- a/relaykit/relayconvert/internal/oai_chat/to_claude_messages_req.go +++ b/relaykit/relayconvert/internal/oai_chat/to_claude_messages_req.go @@ -32,25 +32,14 @@ func OpenAIChatRequestToClaudeMessages(c context.Context, info convmeta.Meta, te claudeTools := make([]any, 0, len(textRequest.Tools)) for _, tool := range textRequest.Tools { - if params, ok := tool.Function.Parameters.(map[string]any); ok { - claudeTool := dto.Tool{ - Name: tool.Function.Name, - Description: tool.Function.Description, - } - claudeTool.InputSchema = make(map[string]interface{}) - if params["type"] != nil { - claudeTool.InputSchema["type"] = params["type"].(string) - } - claudeTool.InputSchema["properties"] = params["properties"] - claudeTool.InputSchema["required"] = params["required"] - for key, value := range params { - if key == "type" || key == "properties" || key == "required" { - continue - } - claudeTool.InputSchema[key] = value - } - claudeTools = append(claudeTools, &claudeTool) + if _, ok := tool.Function.Parameters.(map[string]any); !ok && tool.Type != "function" { + continue } + claudeTools = append(claudeTools, &dto.Tool{ + Name: tool.Function.Name, + Description: tool.Function.Description, + InputSchema: sharedclaude.FunctionParametersToInputSchema(tool.Function.Parameters), + }) } if textRequest.WebSearchOptions != nil { diff --git a/relaykit/relayconvert/internal/oai_chat/to_claude_messages_req_test.go b/relaykit/relayconvert/internal/oai_chat/to_claude_messages_req_test.go new file mode 100644 index 00000000..c718bd25 --- /dev/null +++ b/relaykit/relayconvert/internal/oai_chat/to_claude_messages_req_test.go @@ -0,0 +1,81 @@ +package oaichat + +import ( + "context" + "testing" + + "github.com/QuantumNous/new-api/relaykit/dto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestOpenAIChatRequestToClaudeMessagesNormalizesToolInputSchema(t *testing.T) { + tests := []struct { + name string + parameters any + wantSchema map[string]any + }{ + { + name: "omitted parameters", + parameters: nil, + wantSchema: map[string]any{ + "type": "object", + "properties": map[string]any{}, + }, + }, + { + name: "missing type and properties", + parameters: map[string]any{ + "additionalProperties": false, + }, + wantSchema: map[string]any{ + "type": "object", + "properties": map[string]any{}, + "additionalProperties": false, + }, + }, + { + name: "non-string type", + parameters: map[string]any{ + "type": 123, + "properties": map[string]any{}, + }, + wantSchema: map[string]any{ + "type": 123, + "properties": map[string]any{}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + maxTokens := uint(1024) + got, err := OpenAIChatRequestToClaudeMessages(context.Background(), nil, dto.GeneralOpenAIRequest{ + Model: "claude-test", + MaxTokens: &maxTokens, + Messages: []dto.Message{ + {Role: "user", Content: "Call the tool."}, + }, + Tools: []dto.ToolCallRequest{ + { + Type: "function", + Function: dto.FunctionRequest{ + Name: "get_current_time", + Description: "Get the current time", + Parameters: tt.parameters, + }, + }, + }, + }) + + require.NoError(t, err) + tools, ok := got.Tools.([]any) + require.True(t, ok) + require.Len(t, tools, 1) + tool, ok := tools[0].(*dto.Tool) + require.True(t, ok) + assert.Equal(t, "get_current_time", tool.Name) + assert.Equal(t, tt.wantSchema, tool.InputSchema) + }) + } +} diff --git a/relaykit/relayconvert/internal/oai_responses/to_claude_messages_req.go b/relaykit/relayconvert/internal/oai_responses/to_claude_messages_req.go index 53aa674c..3695449f 100644 --- a/relaykit/relayconvert/internal/oai_responses/to_claude_messages_req.go +++ b/relaykit/relayconvert/internal/oai_responses/to_claude_messages_req.go @@ -134,32 +134,12 @@ func responsesFunctionDeclarationsToClaudeTools(functions []dto.FunctionRequest) tools = append(tools, &dto.Tool{ Name: function.Name, Description: function.Description, - InputSchema: responsesFunctionParametersToClaudeInputSchema(function.Parameters), + InputSchema: sharedclaude.FunctionParametersToInputSchema(function.Parameters), }) } return tools } -func responsesFunctionParametersToClaudeInputSchema(parameters any) map[string]interface{} { - if params, ok := parameters.(map[string]any); ok { - schema := make(map[string]interface{}, len(params)) - for key, value := range params { - schema[key] = value - } - if schema["type"] == nil { - schema["type"] = "object" - } - if schema["properties"] == nil { - schema["properties"] = map[string]interface{}{} - } - return schema - } - return map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{}, - } -} - func applyResponsesReasoningToClaude(req *dto.OpenAIResponsesRequest, claudeRequest *dto.ClaudeRequest) { effort := ReasoningEffort(req) switch effort { diff --git a/relaykit/relayconvert/internal/shared/claude/schema.go b/relaykit/relayconvert/internal/shared/claude/schema.go new file mode 100644 index 00000000..4878130d --- /dev/null +++ b/relaykit/relayconvert/internal/shared/claude/schema.go @@ -0,0 +1,16 @@ +package claude + +func FunctionParametersToInputSchema(parameters any) map[string]any { + params, _ := parameters.(map[string]any) + schema := make(map[string]any, len(params)+2) + for key, value := range params { + schema[key] = value + } + if schema["type"] == nil { + schema["type"] = "object" + } + if schema["properties"] == nil { + schema["properties"] = map[string]any{} + } + return schema +}