mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-11-08 08:55:22 +00:00
Preserve Chat Completions parallel_tool_calls when converting requests to the Responses API, and map Responses parallel_tool_calls back when falling back to Chat Completions upstreams. Cover both true and explicit false values so clients can disable parallel tool calls without the field being dropped by omitempty.
157 lines
4.4 KiB
Go
157 lines
4.4 KiB
Go
package apicompat
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestResponsesInputToChatMessages_DeveloperRoleMapsToSystem(t *testing.T) {
|
|
messages, err := responsesInputToChatMessages("", json.RawMessage(`[{"role":"developer","content":"follow project instructions"}]`))
|
|
require.NoError(t, err)
|
|
require.Len(t, messages, 1)
|
|
|
|
assert.Equal(t, "system", messages[0].Role)
|
|
assert.JSONEq(t, `"follow project instructions"`, string(messages[0].Content))
|
|
}
|
|
|
|
func TestResponsesInputToChatMessages_KeepsChatCompletionRoles(t *testing.T) {
|
|
input := json.RawMessage(`[
|
|
{"role":"system","content":"system message"},
|
|
{"role":"user","content":"user message"},
|
|
{"role":"assistant","content":"assistant message"},
|
|
{"role":"tool","content":"tool message"}
|
|
]`)
|
|
|
|
messages, err := responsesInputToChatMessages("", input)
|
|
require.NoError(t, err)
|
|
require.Len(t, messages, 4)
|
|
|
|
assert.Equal(t, []string{"system", "user", "assistant", "tool"}, chatMessageRoles(messages))
|
|
}
|
|
|
|
func TestResponsesInputToChatMessages_EmptyRoleFallsBackToUser(t *testing.T) {
|
|
messages, err := responsesInputToChatMessages("", json.RawMessage(`[{"role":"","content":"hello"}]`))
|
|
require.NoError(t, err)
|
|
require.Len(t, messages, 1)
|
|
|
|
assert.Equal(t, "user", messages[0].Role)
|
|
}
|
|
|
|
func TestResponsesInputToChatMessages_DeveloperRoleTrimAndCaseInsensitive(t *testing.T) {
|
|
input := json.RawMessage(`[
|
|
{"role":" Developer ","content":"one"},
|
|
{"role":"\tDEVELOPER\n","content":"two"}
|
|
]`)
|
|
|
|
messages, err := responsesInputToChatMessages("", input)
|
|
require.NoError(t, err)
|
|
require.Len(t, messages, 2)
|
|
|
|
assert.Equal(t, []string{"system", "system"}, chatMessageRoles(messages))
|
|
}
|
|
|
|
func TestResponsesToChatCompletionsRequest_InstructionsAndInputDeveloperRole(t *testing.T) {
|
|
req := &ResponsesRequest{
|
|
Model: "gpt-4o",
|
|
Instructions: "Use concise answers.",
|
|
Input: json.RawMessage(`[
|
|
{"role":"developer","content":[{"type":"input_text","text":"Prefer JSON."}]},
|
|
{"role":"user","content":"Hello"}
|
|
]`),
|
|
}
|
|
|
|
out, err := ResponsesToChatCompletionsRequest(req)
|
|
require.NoError(t, err)
|
|
require.Len(t, out.Messages, 3)
|
|
|
|
assert.Equal(t, []string{"system", "system", "user"}, chatMessageRoles(out.Messages))
|
|
assert.JSONEq(t, `"Use concise answers."`, string(out.Messages[0].Content))
|
|
assert.JSONEq(t, `"Prefer JSON."`, string(out.Messages[1].Content))
|
|
assert.JSONEq(t, `"Hello"`, string(out.Messages[2].Content))
|
|
}
|
|
|
|
func TestResponsesToChatCompletionsRequest_TextFormatJsonObject(t *testing.T) {
|
|
req := &ResponsesRequest{
|
|
Model: "gpt-4o",
|
|
Input: json.RawMessage(`[
|
|
{"role":"user","content":"Return JSON"}
|
|
]`),
|
|
Text: &ResponsesText{
|
|
Format: json.RawMessage(`{"type":"json_object"}`),
|
|
},
|
|
}
|
|
|
|
out, err := ResponsesToChatCompletionsRequest(req)
|
|
require.NoError(t, err)
|
|
assert.JSONEq(t, `{"type":"json_object"}`, string(out.ResponseFormat))
|
|
}
|
|
|
|
func TestResponsesToChatCompletionsRequest_TextFormatJsonSchema(t *testing.T) {
|
|
req := &ResponsesRequest{
|
|
Model: "gpt-4o",
|
|
Input: json.RawMessage(`[
|
|
{"role":"user","content":"Return structured JSON"}
|
|
]`),
|
|
Text: &ResponsesText{
|
|
Format: json.RawMessage(`{
|
|
"type":"json_schema",
|
|
"name":"answer",
|
|
"schema":{
|
|
"type":"object",
|
|
"properties":{"ok":{"type":"boolean"}},
|
|
"required":["ok"],
|
|
"additionalProperties":false
|
|
},
|
|
"strict":true
|
|
}`),
|
|
},
|
|
}
|
|
|
|
out, err := ResponsesToChatCompletionsRequest(req)
|
|
require.NoError(t, err)
|
|
assert.JSONEq(t, `{
|
|
"type":"json_schema",
|
|
"json_schema":{
|
|
"name":"answer",
|
|
"schema":{
|
|
"type":"object",
|
|
"properties":{"ok":{"type":"boolean"}},
|
|
"required":["ok"],
|
|
"additionalProperties":false
|
|
},
|
|
"strict":true
|
|
}
|
|
}`, string(out.ResponseFormat))
|
|
}
|
|
|
|
func TestResponsesToChatCompletionsRequest_ParallelToolCalls(t *testing.T) {
|
|
parallel := false
|
|
req := &ResponsesRequest{
|
|
Model: "gpt-4o",
|
|
Input: json.RawMessage(`[
|
|
{"role":"user","content":"Use tools"}
|
|
]`),
|
|
ParallelToolCalls: ¶llel,
|
|
}
|
|
|
|
out, err := ResponsesToChatCompletionsRequest(req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, out.ParallelToolCalls)
|
|
assert.False(t, *out.ParallelToolCalls)
|
|
|
|
payload, err := json.Marshal(out)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, string(payload), `"parallel_tool_calls":false`)
|
|
}
|
|
|
|
func chatMessageRoles(messages []ChatMessage) []string {
|
|
roles := make([]string, 0, len(messages))
|
|
for _, message := range messages {
|
|
roles = append(roles, message.Role)
|
|
}
|
|
return roles
|
|
}
|