fix(relaykit): preserve parameterless tools in Claude conversion (#6862)

This commit is contained in:
Seefs
2026-08-18 17:30:59 +08:00
committed by GitHub
parent e2c7aa7b10
commit 3dda1d50c6
4 changed files with 105 additions and 39 deletions
@@ -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 {
@@ -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)
})
}
}
@@ -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 {
@@ -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
}