mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-09-07 00:24:17 +08:00
fix(responses): translate text.format into response_format in request conversion
Closes: #4802
This commit is contained in:
@@ -45,6 +45,13 @@ func ConvertOpenAIResponsesRequestToOpenAIChatCompletions(modelName string, inpu
|
||||
// Set stream configuration
|
||||
out, _ = sjson.SetBytes(out, "stream", stream)
|
||||
|
||||
// Map Responses text format to Chat Completions response format.
|
||||
if textFormat := root.Get("text.format"); textFormat.Exists() {
|
||||
if responseFormat := convertResponsesTextFormatToChatResponseFormat(textFormat); len(responseFormat) > 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "response_format", responseFormat)
|
||||
}
|
||||
}
|
||||
|
||||
// Map generation parameters from responses format to chat completions format
|
||||
if maxTokens := root.Get("max_output_tokens"); maxTokens.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "max_tokens", maxTokens.Int())
|
||||
@@ -344,6 +351,29 @@ func ConvertOpenAIResponsesRequestToOpenAIChatCompletions(modelName string, inpu
|
||||
return out
|
||||
}
|
||||
|
||||
func convertResponsesTextFormatToChatResponseFormat(textFormat gjson.Result) []byte {
|
||||
formatType := textFormat.Get("type").String()
|
||||
switch formatType {
|
||||
case "text", "json_object":
|
||||
responseFormat := []byte(`{"type":""}`)
|
||||
responseFormat, _ = sjson.SetBytes(responseFormat, "type", formatType)
|
||||
return responseFormat
|
||||
case "json_schema":
|
||||
responseFormat := []byte(`{"type":"json_schema","json_schema":{}}`)
|
||||
for _, field := range []string{"name", "description", "strict"} {
|
||||
if value := textFormat.Get(field); value.Exists() {
|
||||
responseFormat, _ = sjson.SetBytes(responseFormat, "json_schema."+field, value.Value())
|
||||
}
|
||||
}
|
||||
if schema := textFormat.Get("schema"); schema.Exists() {
|
||||
responseFormat, _ = sjson.SetRawBytes(responseFormat, "json_schema.schema", []byte(schema.Raw))
|
||||
}
|
||||
return responseFormat
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func setFunctionCallOutputContent(toolMessage []byte, output gjson.Result) []byte {
|
||||
structuredContent := output
|
||||
if output.Type == gjson.String {
|
||||
|
||||
@@ -581,6 +581,74 @@ func TestConvertOpenAIResponsesRequestToOpenAIChatCompletions_PreservesParallelT
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIResponsesRequestToOpenAIChatCompletions_PreservesJSONSchemaTextFormat(t *testing.T) {
|
||||
raw := []byte(`{
|
||||
"text": {
|
||||
"format": {
|
||||
"type": "json_schema",
|
||||
"name": "answer",
|
||||
"description": "Structured answer",
|
||||
"strict": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ok": {"type": "boolean"}
|
||||
},
|
||||
"required": ["ok"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
out := ConvertOpenAIResponsesRequestToOpenAIChatCompletions("deepseek-v4-flash", raw, false)
|
||||
|
||||
if got := gjson.GetBytes(out, "response_format.type").String(); got != "json_schema" {
|
||||
t.Fatalf("response_format.type = %q, want json_schema; output=%s", got, out)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "response_format.json_schema.name").String(); got != "answer" {
|
||||
t.Fatalf("response_format.json_schema.name = %q, want answer; output=%s", got, out)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "response_format.json_schema.description").String(); got != "Structured answer" {
|
||||
t.Fatalf("response_format.json_schema.description = %q, want Structured answer; output=%s", got, out)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "response_format.json_schema.strict"); !got.Exists() || !got.Bool() {
|
||||
t.Fatalf("response_format.json_schema.strict = %v, want true; output=%s", got.Value(), out)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "response_format.json_schema.schema.properties.ok.type").String(); got != "boolean" {
|
||||
t.Fatalf("response_format.json_schema.schema.properties.ok.type = %q, want boolean; output=%s", got, out)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "response_format.json_schema.schema.required.0").String(); got != "ok" {
|
||||
t.Fatalf("response_format.json_schema.schema.required.0 = %q, want ok; output=%s", got, out)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "response_format.json_schema.schema.additionalProperties"); !got.Exists() || got.Bool() {
|
||||
t.Fatalf("response_format.json_schema.schema.additionalProperties = %v, want false; output=%s", got.Value(), out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIResponsesRequestToOpenAIChatCompletions_PreservesJSONObjectTextFormat(t *testing.T) {
|
||||
raw := []byte(`{"text":{"format":{"type":"json_object"}}}`)
|
||||
|
||||
out := ConvertOpenAIResponsesRequestToOpenAIChatCompletions("deepseek-v4-flash", raw, false)
|
||||
|
||||
if got := gjson.GetBytes(out, "response_format.type").String(); got != "json_object" {
|
||||
t.Fatalf("response_format.type = %q, want json_object; output=%s", got, out)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "response_format.json_schema"); got.Exists() {
|
||||
t.Fatalf("response_format.json_schema should be omitted; output=%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIResponsesRequestToOpenAIChatCompletions_OmitsResponseFormatWithoutTextFormat(t *testing.T) {
|
||||
raw := []byte(`{"input":"Return plain text."}`)
|
||||
|
||||
out := ConvertOpenAIResponsesRequestToOpenAIChatCompletions("deepseek-v4-flash", raw, false)
|
||||
|
||||
if got := gjson.GetBytes(out, "response_format"); got.Exists() {
|
||||
t.Fatalf("response_format should be omitted, got %s; output=%s", got.Raw, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIResponsesRequestToOpenAIChatCompletions_NormalizesInputImageDetail(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user