package convert import ( "encoding/json" "strings" ) // --------------------------------------------------------------------------- // 请求:Responses → Chat // responsesToChatReq 将 OpenAI Responses 请求转为 Chat 请求。 func responsesToChatReq(body []byte) ([]byte, error) { var m map[string]json.RawMessage if err := json.Unmarshal(body, &m); err != nil { return nil, err } out := map[string]any{"model": str(rawJSON(m, "model"))} if v, ok := m["stream"]; ok && string(v) == "true" { out["stream"] = true } if v, ok := m["temperature"]; ok { out["temperature"] = v } if v, ok := m["top_p"]; ok { out["top_p"] = v } if v, ok := m["max_output_tokens"]; ok { out["max_tokens"] = v } var msgs []any if ins := str(rawJSON(m, "instructions")); ins != "" { msgs = append(msgs, map[string]any{"role": "system", "content": ins}) } msgs = append(msgs, responsesInputToChat(rawJSON(m, "input"))...) out["messages"] = msgs if raw := rawJSON(m, "tools"); raw != nil { var tools []map[string]any if json.Unmarshal(raw, &tools) == nil { chatTools := make([]any, 0, len(tools)) for _, t := range tools { chatTools = append(chatTools, map[string]any{ "type": "function", "function": map[string]any{ "name": t["name"], "description": t["description"], "parameters": t["parameters"], }, }) } out["tools"] = chatTools } } return json.Marshal(out) } // responsesInputToChat 把 Responses input 转成 Chat messages。 func responsesInputToChat(raw json.RawMessage) []any { if len(raw) == 0 || string(raw) == "null" { return nil } // 字符串输入 if s := str(raw); s != "" { return []any{map[string]any{"role": "user", "content": s}} } var items []map[string]any if err := json.Unmarshal(raw, &items); err != nil || items == nil { return nil } var out []any for _, item := range items { switch item["type"] { case "function_call": var args any _ = json.Unmarshal([]byte(strField(item["arguments"])), &args) out = append(out, map[string]any{ "role": "assistant", "content": "", "tool_calls": []any{map[string]any{ "id": strField(item["call_id"]), "type": "function", "function": map[string]any{ "name": strField(item["name"]), "arguments": strField(item["arguments"]), }, }}, }) case "function_call_output": out = append(out, map[string]any{ "role": "tool", "tool_call_id": strField(item["call_id"]), "content": strField(item["output"]), }) default: // message 条目 role, _ := item["role"].(string) if role == "" { role = "user" } if content, ok := item["content"].(string); ok { out = append(out, map[string]any{"role": role, "content": content}) } else if blocks, ok := item["content"].([]any); ok { var text []string var contentBlocks []any for _, b := range blocks { bm, ok := b.(map[string]any) if !ok { continue } switch bm["type"] { case "input_text", "text": if t, _ := bm["text"].(string); t != "" { text = append(text, t) contentBlocks = append(contentBlocks, map[string]any{"type": "text", "text": t}) } case "input_image": var url string if s, ok := bm["image_url"].(string); ok { url = s } else if m, ok := bm["image_url"].(map[string]any); ok { url, _ = m["url"].(string) } if url != "" { contentBlocks = append(contentBlocks, map[string]any{"type": "image_url", "image_url": map[string]any{"url": url}}) } } } hasImage := false for _, cb := range contentBlocks { if m, _ := cb.(map[string]any); m["type"] == "image_url" { hasImage = true break } } if hasImage { out = append(out, map[string]any{"role": role, "content": contentBlocks}) } else { out = append(out, map[string]any{"role": role, "content": strings.Join(text, "")}) } } } } return out } // chatContentToResponsesBlocks 把 Chat 用户消息 content 转 Responses input 块数组(input_text / input_image)。 func chatContentToResponsesBlocks(content json.RawMessage) []any { // 纯字符串 → 单个 input_text var s string if json.Unmarshal(content, &s) == nil && s != "" { return []any{map[string]any{"type": "input_text", "text": s}} } // 数组 → 按块转换(text / image_url) var arr []map[string]any if json.Unmarshal(content, &arr) == nil && arr != nil { var out []any for _, b := range arr { switch b["type"] { case "text", "input_text": if t, _ := b["text"].(string); t != "" { out = append(out, map[string]any{"type": "input_text", "text": t}) } case "image_url": var url string if iu, ok := b["image_url"].(map[string]any); ok { url, _ = iu["url"].(string) } else if s, ok := b["image_url"].(string); ok { url = s } if url != "" { out = append(out, map[string]any{"type": "input_image", "image_url": url}) } } } return out } return nil } // --------------------------------------------------------------------------- // 请求:Chat → Responses // chatToResponsesReq 将 Chat 请求转为 Responses 请求。 func chatToResponsesReq(body []byte) ([]byte, error) { var req chatReq if err := json.Unmarshal(body, &req); err != nil { return nil, err } out := map[string]any{"model": req.Model} if req.Stream { out["stream"] = true } if req.Temperature != nil { out["temperature"] = *req.Temperature } if req.TopP != nil { out["top_p"] = *req.TopP } if req.MaxTokens != nil { out["max_output_tokens"] = *req.MaxTokens } var system []string var input []any for _, m := range req.Messages { if m.Role == "system" { if s := str(m.Content); s != "" { system = append(system, s) } continue } switch m.Role { case "tool": input = append(input, map[string]any{ "type": "function_call_output", "call_id": m.ToolCallID, "output": str(m.Content), }) case "assistant": if len(m.ToolCalls) > 0 { for _, tc := range m.ToolCalls { input = append(input, map[string]any{ "type": "function_call", "call_id": tc.ID, "name": tc.Function.Name, "arguments": tc.Function.Arguments, }) } } else if s := str(m.Content); s != "" { input = append(input, map[string]any{"type": "message", "role": "assistant", "content": []any{ map[string]any{"type": "input_text", "text": s}, }}) } default: if blocks := chatContentToResponsesBlocks(m.Content); len(blocks) > 0 { input = append(input, map[string]any{"type": "message", "role": "user", "content": blocks}) } } } if len(system) > 0 { out["instructions"] = strings.Join(system, "\n") } if len(input) == 1 { out["input"] = input[0] // 单条消息项 } else { out["input"] = input } if len(req.Tools) > 0 { tools := make([]any, 0, len(req.Tools)) for _, t := range req.Tools { tools = append(tools, map[string]any{ "type": "function", "name": t.Function.Name, "description": t.Function.Description, "parameters": rawOrObject(t.Function.Parameters), }) } out["tools"] = tools } return json.Marshal(out) } // --------------------------------------------------------------------------- // 响应:Responses → Chat // responsesToChatResp 将 Responses 响应(非流式)转为 Chat 响应。 func responsesToChatResp(body []byte) ([]byte, error) { var m map[string]json.RawMessage if err := json.Unmarshal(body, &m); err != nil { return nil, err } var text string var toolCalls []any if raw := rawJSON(m, "output"); raw != nil { var outputs []map[string]any if json.Unmarshal(raw, &outputs) == nil { for _, o := range outputs { switch o["type"] { case "message": if content, ok := o["content"].([]any); ok { for _, c := range content { if cm, ok := c.(map[string]any); ok { if t, _ := cm["text"].(string); t != "" { text += t } } } } case "function_call": toolCalls = append(toolCalls, map[string]any{ "id": strField(o["call_id"]), "type": "function", "function": map[string]any{ "name": strField(o["name"]), "arguments": strField(o["arguments"]), }, }) } } } } msg := map[string]any{"role": "assistant", "content": text} if len(toolCalls) > 0 { msg["tool_calls"] = toolCalls } finish := "stop" switch { case string(rawJSON(m, "status")) == `"incomplete"`: finish = "length" // 截断优先,客户端可据此区分 case len(toolCalls) > 0: finish = "tool_calls" // 有工具调用时映射 tool_calls,否则中转后 stop_reason 成 end_turn } var prompt, completion int64 if u := rawJSON(m, "usage"); u != nil { var us struct { InputTokens int64 `json:"input_tokens"` OutputTokens int64 `json:"output_tokens"` } _ = json.Unmarshal(u, &us) prompt, completion = us.InputTokens, us.OutputTokens } return json.Marshal(map[string]any{ "id": "chatcmpl-" + strings.TrimPrefix(str(rawJSON(m, "id")), "resp_"), "object": "chat.completion", "model": str(rawJSON(m, "model")), "choices": []any{map[string]any{"index": 0, "message": msg, "finish_reason": finish}}, "usage": map[string]any{ "prompt_tokens": prompt, "completion_tokens": completion, "total_tokens": prompt + completion, }, }) } // --------------------------------------------------------------------------- // 响应:Chat → Responses // chatToResponsesResp 将 Chat 响应(非流式)转为 Responses 响应。 func chatToResponsesResp(body []byte) ([]byte, error) { var r chatResp if err := json.Unmarshal(body, &r); err != nil { return nil, err } output := make([]any, 0, 2) var finish = "completed" if len(r.Choices) > 0 { msg := r.Choices[0].Message if msg.Content != "" { output = append(output, map[string]any{ "type": "message", "role": "assistant", "content": []any{map[string]any{"type": "output_text", "text": msg.Content}}, }) } for _, tc := range msg.ToolCalls { output = append(output, map[string]any{ "type": "function_call", "call_id": tc.ID, "name": tc.Function.Name, "arguments": tc.Function.Arguments, }) } if r.Choices[0].FinishReason == "length" { finish = "incomplete" } } status := finish return json.Marshal(map[string]any{ "id": "resp_" + strings.TrimPrefix(r.ID, "chatcmpl-"), "object": "response", "model": r.Model, "status": status, "output": output, "usage": map[string]any{ "input_tokens": r.Usage.PromptTokens, "output_tokens": r.Usage.CompletionTokens, "total_tokens": r.Usage.PromptTokens + r.Usage.CompletionTokens, }, }) }