package convert import ( "encoding/json" "strings" ) // --------------------------------------------------------------------------- // 请求:Chat → Messages type chatTool struct { Type string `json:"type"` Function struct { Name string `json:"name"` Description string `json:"description"` Parameters json.RawMessage `json:"parameters"` } `json:"function"` } type chatMsg struct { Role string `json:"role"` Content json.RawMessage `json:"content"` ToolCallID string `json:"tool_call_id"` ToolCalls []struct { ID string `json:"id"` Function struct { Name string `json:"name"` Arguments string `json:"arguments"` } `json:"function"` } `json:"tool_calls"` } type chatReq struct { Model string `json:"model"` Messages []chatMsg `json:"messages"` Tools []chatTool `json:"tools"` Temperature *float64 `json:"temperature"` TopP *float64 `json:"top_p"` MaxTokens *int `json:"max_tokens"` Stop []string `json:"stop"` Stream bool `json:"stream"` } // chatToMessagesReq 将 OpenAI Chat 请求转为 Anthropic Messages 请求。 func chatToMessagesReq(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, "max_tokens": intOrNil(req.MaxTokens, 1024), // Anthropic 必填 } if req.Stream { out["stream"] = true } if req.Temperature != nil { out["temperature"] = *req.Temperature } if req.TopP != nil { out["top_p"] = *req.TopP } if len(req.Stop) > 0 { out["stop_sequences"] = req.Stop } var system []string msgs := make([]any, 0, len(req.Messages)) for _, m := range req.Messages { if m.Role == "system" { if s := str(m.Content); s != "" { system = append(system, s) } continue } msgs = append(msgs, chatMsgToAnthropic(m)) } if len(system) > 0 { out["system"] = strings.Join(system, "\n") } out["messages"] = msgs if len(req.Tools) > 0 { tools := make([]any, 0, len(req.Tools)) for _, t := range req.Tools { var params any if len(t.Function.Parameters) > 0 && string(t.Function.Parameters) != "null" { _ = json.Unmarshal(t.Function.Parameters, ¶ms) } tools = append(tools, map[string]any{ "name": t.Function.Name, "description": t.Function.Description, "input_schema": params, }) } out["tools"] = tools } return json.Marshal(out) } // chatMsgToAnthropic 单条消息转 Anthropic 内容。 func chatMsgToAnthropic(m chatMsg) any { switch m.Role { case "assistant": content := make([]any, 0, 2) if s := str(m.Content); s != "" { content = append(content, map[string]any{"type": "text", "text": s}) } for _, tc := range m.ToolCalls { var input any if tc.Function.Arguments != "" { _ = json.Unmarshal([]byte(tc.Function.Arguments), &input) } content = append(content, map[string]any{ "type": "tool_use", "id": tc.ID, "name": tc.Function.Name, "input": input, }) } return map[string]any{"role": "assistant", "content": content} case "tool": return map[string]any{"role": "user", "content": []any{ map[string]any{"type": "tool_result", "tool_use_id": m.ToolCallID, "content": str(m.Content)}, }} default: // user var arr []map[string]any if json.Unmarshal(m.Content, &arr) == nil && arr != nil { blocks := make([]any, 0, len(arr)) for _, b := range arr { switch b["type"] { case "text", "input_text": if t, _ := b["text"].(string); t != "" { blocks = append(blocks, map[string]any{"type": "text", "text": t}) } case "image_url": var url string if iu, ok := b["image_url"].(map[string]any); ok { url, _ = iu["url"].(string) } if url != "" { blocks = append(blocks, map[string]any{"type": "image", "source": map[string]any{"type": "url", "url": url}}) } } } if len(blocks) > 0 { return map[string]any{"role": "user", "content": blocks} } } return map[string]any{"role": "user", "content": str(m.Content)} } } // --------------------------------------------------------------------------- // 请求:Messages → Chat type messagesReq struct { Model string `json:"model"` System json.RawMessage `json:"system"` Messages []struct { Role string `json:"role"` Content json.RawMessage `json:"content"` } `json:"messages"` Tools []struct { Name string `json:"name"` Description string `json:"description"` InputSchema json.RawMessage `json:"input_schema"` } `json:"tools"` Temperature *float64 `json:"temperature"` TopP *float64 `json:"top_p"` MaxTokens *int `json:"max_tokens"` StopSequence []string `json:"stop_sequences"` Stream bool `json:"stream"` } // messagesToChatReq 将 Anthropic Messages 请求转为 OpenAI Chat 请求。 func messagesToChatReq(body []byte) ([]byte, error) { var req messagesReq 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_tokens"] = *req.MaxTokens } if len(req.StopSequence) > 0 { out["stop"] = req.StopSequence } msgs := make([]any, 0, len(req.Messages)+1) if s := str(req.System); s != "" { msgs = append(msgs, map[string]any{"role": "system", "content": s}) } for _, m := range req.Messages { msgs = append(msgs, anthropicMsgToChat(m.Role, m.Content)...) } out["messages"] = msgs 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", "function": map[string]any{ "name": t.Name, "description": t.Description, "parameters": rawOrObject(t.InputSchema), }, }) } out["tools"] = tools } return json.Marshal(out) } // anthropicMsgToChat 将一条 Anthropic 消息拆成 0..N 条 Chat 消息。 func anthropicMsgToChat(role string, content json.RawMessage) []any { // 块数组优先(tool_use / tool_result 需要分块解析) var blocks []map[string]any if json.Unmarshal(content, &blocks) == nil && blocks != nil { var out []any var textParts []string var toolCalls []any for _, b := range blocks { switch b["type"] { case "text": if t, _ := b["text"].(string); t != "" { textParts = append(textParts, t) } case "tool_use": id, _ := b["id"].(string) name, _ := b["name"].(string) args, _ := json.Marshal(b["input"]) toolCalls = append(toolCalls, map[string]any{ "id": id, "type": "function", "function": map[string]any{ "name": name, "arguments": string(args), }, }) case "tool_result": callID, _ := b["tool_use_id"].(string) res := strField(b["content"]) out = append(out, map[string]any{"role": "tool", "tool_call_id": callID, "content": res}) } } if len(textParts) > 0 || len(toolCalls) > 0 { msg := map[string]any{"role": role} if len(textParts) > 0 { msg["content"] = strings.Join(textParts, "") } if len(toolCalls) > 0 { msg["tool_calls"] = toolCalls } out = append(out, msg) } if len(out) > 0 { return out } } // 纯文本 if s := str(content); s != "" { return []any{map[string]any{"role": role, "content": s}} } return nil } // --------------------------------------------------------------------------- // 响应:Messages → Chat type messagesResp struct { ID string `json:"id"` Model string `json:"model"` Content []struct { Type string `json:"type"` Text string `json:"text"` ID string `json:"id"` Name string `json:"name"` Input json.RawMessage `json:"input"` } `json:"content"` StopReason string `json:"stop_reason"` Usage struct { InputTokens int64 `json:"input_tokens"` OutputTokens int64 `json:"output_tokens"` } `json:"usage"` } // messagesToChatResp 将 Anthropic Messages 响应(非流式)转为 Chat 响应。 func messagesToChatResp(body []byte) ([]byte, error) { var r messagesResp if err := json.Unmarshal(body, &r); err != nil { return nil, err } var text string var toolCalls []any for _, c := range r.Content { switch c.Type { case "text": text += c.Text case "tool_use": args, _ := json.Marshal(c.Input) toolCalls = append(toolCalls, map[string]any{ "id": c.ID, "type": "function", "function": map[string]any{ "name": c.Name, "arguments": string(args), }, }) } } msg := map[string]any{"role": "assistant", "content": text} if len(toolCalls) > 0 { msg["tool_calls"] = toolCalls } return json.Marshal(map[string]any{ "id": "chatcmpl-" + strings.TrimPrefix(r.ID, "msg_"), "object": "chat.completion", "model": r.Model, "created": 0, "choices": []any{map[string]any{ "index": 0, "message": msg, "finish_reason": messagesStopToChat(r.StopReason), }}, "usage": map[string]any{ "prompt_tokens": r.Usage.InputTokens, "completion_tokens": r.Usage.OutputTokens, "total_tokens": r.Usage.InputTokens + r.Usage.OutputTokens, }, }) } // --------------------------------------------------------------------------- // 响应:Chat → Messages type chatResp struct { ID string `json:"id"` Model string `json:"model"` Choices []struct { Message struct { Role string `json:"role"` Content string `json:"content"` ToolCalls []struct { ID string `json:"id"` Function struct { Name string `json:"name"` Arguments string `json:"arguments"` } `json:"function"` } `json:"tool_calls"` } `json:"message"` FinishReason string `json:"finish_reason"` } `json:"choices"` Usage struct { PromptTokens int64 `json:"prompt_tokens"` CompletionTokens int64 `json:"completion_tokens"` } `json:"usage"` } // chatToMessagesResp 将 Chat 响应(非流式)转为 Messages 响应。 func chatToMessagesResp(body []byte) ([]byte, error) { var r chatResp if err := json.Unmarshal(body, &r); err != nil { return nil, err } content := make([]any, 0, 2) var finish = "end_turn" if len(r.Choices) > 0 { msg := r.Choices[0].Message if msg.Content != "" { content = append(content, map[string]any{"type": "text", "text": msg.Content}) } for _, tc := range msg.ToolCalls { var input any _ = json.Unmarshal([]byte(tc.Function.Arguments), &input) content = append(content, map[string]any{ "type": "tool_use", "id": tc.ID, "name": tc.Function.Name, "input": input, }) } finish = chatStopToMessages(r.Choices[0].FinishReason) } return json.Marshal(map[string]any{ "id": "msg_" + strings.TrimPrefix(r.ID, "chatcmpl-"), "type": "message", "role": "assistant", "model": r.Model, "content": content, "stop_reason": finish, "usage": map[string]any{ "input_tokens": r.Usage.PromptTokens, "output_tokens": r.Usage.CompletionTokens, }, }) } // --------------------------------------------------------------------------- // 辅助 func intOrNil(p *int, def int) any { if p == nil { return def } return *p } func rawOrObject(raw json.RawMessage) any { if len(raw) == 0 || string(raw) == "null" { return map[string]any{} } var m map[string]any if json.Unmarshal(raw, &m) == nil { return m } return map[string]any{} } func strField(v any) string { if s, ok := v.(string); ok { return s } return "" } func messagesStopToChat(s string) string { switch s { case "tool_use": return "tool_calls" case "max_tokens": return "length" default: return "stop" } } func chatStopToMessages(s string) string { switch s { case "tool_calls": return "tool_use" case "length": return "max_tokens" default: return "end_turn" } }