diff --git a/common/json.go b/common/json.go index 54f8baa3..1625be6d 100644 --- a/common/json.go +++ b/common/json.go @@ -43,3 +43,19 @@ func GetJsonType(data json.RawMessage) string { return "number" } } + +// JsonRawMessageToString returns JSON strings as their decoded value and other JSON values as raw text. +func JsonRawMessageToString(data json.RawMessage) string { + trimmed := bytes.TrimSpace(data) + if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) { + return "" + } + if trimmed[0] != '"' { + return string(trimmed) + } + var value string + if err := Unmarshal(trimmed, &value); err != nil { + return string(trimmed) + } + return value +} diff --git a/common/json_test.go b/common/json_test.go new file mode 100644 index 00000000..b5994945 --- /dev/null +++ b/common/json_test.go @@ -0,0 +1,43 @@ +package common + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestJsonRawMessageToString(t *testing.T) { + tests := []struct { + name string + data json.RawMessage + want string + }{ + { + name: "object", + data: json.RawMessage(`{"city":"Paris","days":0,"strict":false}`), + want: `{"city":"Paris","days":0,"strict":false}`, + }, + { + name: "string", + data: json.RawMessage(`"{\"city\":\"Paris\",\"days\":0,\"strict\":false}"`), + want: `{"city":"Paris","days":0,"strict":false}`, + }, + { + name: "null", + data: json.RawMessage(`null`), + want: "", + }, + { + name: "empty", + data: nil, + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, JsonRawMessageToString(tt.data)) + }) + } +} diff --git a/dto/openai_response.go b/dto/openai_response.go index 8d727dab..0e6b818d 100644 --- a/dto/openai_response.go +++ b/dto/openai_response.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/types" ) @@ -346,7 +347,20 @@ type ResponsesOutput struct { Size string `json:"size"` CallId string `json:"call_id,omitempty"` Name string `json:"name,omitempty"` - Arguments string `json:"arguments,omitempty"` + Arguments json.RawMessage `json:"arguments,omitempty"` +} + +// ArgumentsString returns function call arguments in the string form expected by Chat Completions. +func (r *ResponsesOutput) ArgumentsString() string { + if r == nil { + return "" + } + return ResponsesArgumentsString(r.Arguments) +} + +// ResponsesArgumentsString returns function call arguments in the string form expected by Chat Completions. +func ResponsesArgumentsString(arguments json.RawMessage) string { + return common.JsonRawMessageToString(arguments) } type ResponsesOutputContent struct { diff --git a/relay/channel/openai/chat_via_responses.go b/relay/channel/openai/chat_via_responses.go index 5e8ec173..2c075227 100644 --- a/relay/channel/openai/chat_via_responses.go +++ b/relay/channel/openai/chat_via_responses.go @@ -408,7 +408,7 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo toolCallNameByID[callID] = name } - newArgs := streamResp.Item.Arguments + newArgs := streamResp.Item.ArgumentsString() prevArgs := toolCallArgsByID[callID] argsDelta := "" if newArgs != "" { diff --git a/service/openaicompat/responses_to_chat.go b/service/openaicompat/responses_to_chat.go index abd03592..d1c7473f 100644 --- a/service/openaicompat/responses_to_chat.go +++ b/service/openaicompat/responses_to_chat.go @@ -60,7 +60,7 @@ func ResponsesResponseToChatCompletionsResponse(resp *dto.OpenAIResponsesRespons Type: "function", Function: dto.FunctionResponse{ Name: name, - Arguments: out.Arguments, + Arguments: out.ArgumentsString(), }, }) }