mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Merge pull request #7785 from SPHINX998/fix/apicompat-chat-named-tool-choice
fix(apicompat): flatten named chat tool_choice for Responses
This commit is contained in:
@@ -88,10 +88,11 @@ func ChatCompletionsToResponses(req *ChatCompletionsRequest) (*ResponsesRequest,
|
||||
out.Tools = convertChatToolsToResponses(req.Tools, req.Functions)
|
||||
}
|
||||
|
||||
// tool_choice: already compatible format — pass through directly.
|
||||
// tool_choice: strings and Responses-shaped objects pass through; a named
|
||||
// Chat choice nests the name under "function" and must be flattened.
|
||||
// Legacy function_call needs mapping.
|
||||
if len(req.ToolChoice) > 0 {
|
||||
out.ToolChoice = req.ToolChoice
|
||||
out.ToolChoice = convertChatToolChoiceToResponses(req.ToolChoice)
|
||||
} else if len(req.FunctionCall) > 0 {
|
||||
tc, err := convertChatFunctionCallToToolChoice(req.FunctionCall)
|
||||
if err != nil {
|
||||
@@ -490,6 +491,34 @@ func defaultStrictFalse(src *bool) *bool {
|
||||
return src
|
||||
}
|
||||
|
||||
// convertChatToolChoiceToResponses maps a Chat Completions tool_choice to the
|
||||
// Responses API shape.
|
||||
//
|
||||
// {"type":"function","function":{"name":"X"}} → {"type":"function","name":"X"}
|
||||
//
|
||||
// Strings ("auto", "none", "required") and objects that already use the
|
||||
// Responses shape are returned unchanged.
|
||||
func convertChatToolChoiceToResponses(raw json.RawMessage) json.RawMessage {
|
||||
var choice struct {
|
||||
Type string `json:"type"`
|
||||
Function *struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"function"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &choice); err != nil || choice.Type != "function" || choice.Function == nil {
|
||||
return raw
|
||||
}
|
||||
name := strings.TrimSpace(choice.Function.Name)
|
||||
if name == "" {
|
||||
return raw
|
||||
}
|
||||
flat, err := json.Marshal(map[string]string{"type": "function", "name": name})
|
||||
if err != nil {
|
||||
return raw
|
||||
}
|
||||
return flat
|
||||
}
|
||||
|
||||
// convertChatFunctionCallToToolChoice maps the legacy function_call field to a
|
||||
// Responses API tool_choice value.
|
||||
//
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package apicompat
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Chat Completions nests a named choice under "function"; the Responses API
|
||||
// expects the name at the top level and rejects the nested shape.
|
||||
func TestChatCompletionsToResponses_ToolChoiceShape(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
choice string
|
||||
want string
|
||||
}{
|
||||
{name: "named chat function is flattened", choice: `{"type":"function","function":{"name":"get_weather"}}`, want: `{"type":"function","name":"get_weather"}`},
|
||||
{name: "flat responses function is kept", choice: `{"type":"function","name":"get_weather"}`, want: `{"type":"function","name":"get_weather"}`},
|
||||
{name: "auto string is kept", choice: `"auto"`, want: `"auto"`},
|
||||
{name: "required string is kept", choice: `"required"`, want: `"required"`},
|
||||
{name: "none string is kept", choice: `"none"`, want: `"none"`},
|
||||
{name: "allowed_tools policy is kept", choice: `{"type":"allowed_tools","mode":"auto","tools":[{"type":"function","name":"get_weather"}]}`, want: `{"type":"allowed_tools","mode":"auto","tools":[{"type":"function","name":"get_weather"}]}`},
|
||||
{name: "function without name is kept", choice: `{"type":"function","function":{}}`, want: `{"type":"function","function":{}}`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := &ChatCompletionsRequest{
|
||||
Model: "gpt-4o",
|
||||
Messages: []ChatMessage{{Role: "user", Content: json.RawMessage(`"weather?"`)}},
|
||||
Tools: []ChatTool{{
|
||||
Type: "function",
|
||||
Function: &ChatFunction{Name: "get_weather", Parameters: json.RawMessage(`{"type":"object"}`)},
|
||||
}},
|
||||
ToolChoice: json.RawMessage(tt.choice),
|
||||
}
|
||||
|
||||
resp, err := ChatCompletionsToResponses(req)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, tt.want, string(resp.ToolChoice))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user