mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
Add Qwen support
This commit is contained in:
253
internal/translator/openai/claude/openai_claude_request.go
Normal file
253
internal/translator/openai/claude/openai_claude_request.go
Normal file
@@ -0,0 +1,253 @@
|
||||
// Package claude provides request translation functionality for Anthropic to OpenAI API.
|
||||
// It handles parsing and transforming Anthropic API requests into OpenAI Chat Completions API format,
|
||||
// extracting model information, system instructions, message contents, and tool declarations.
|
||||
// The package performs JSON data transformation to ensure compatibility
|
||||
// between Anthropic API format and OpenAI API's expected format.
|
||||
package claude
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
// ConvertAnthropicRequestToOpenAI parses and transforms an Anthropic API request into OpenAI Chat Completions API format.
|
||||
// It extracts the model name, system instruction, message contents, and tool declarations
|
||||
// from the raw JSON request and returns them in the format expected by the OpenAI API.
|
||||
func ConvertAnthropicRequestToOpenAI(rawJSON []byte) string {
|
||||
// Base OpenAI Chat Completions API template
|
||||
out := `{"model":"","messages":[]}`
|
||||
|
||||
root := gjson.ParseBytes(rawJSON)
|
||||
|
||||
// Model mapping
|
||||
if model := root.Get("model"); model.Exists() {
|
||||
modelStr := model.String()
|
||||
out, _ = sjson.Set(out, "model", modelStr)
|
||||
}
|
||||
|
||||
// Max tokens
|
||||
if maxTokens := root.Get("max_tokens"); maxTokens.Exists() {
|
||||
out, _ = sjson.Set(out, "max_tokens", maxTokens.Int())
|
||||
}
|
||||
|
||||
// Temperature
|
||||
if temp := root.Get("temperature"); temp.Exists() {
|
||||
out, _ = sjson.Set(out, "temperature", temp.Float())
|
||||
}
|
||||
|
||||
// Top P
|
||||
if topP := root.Get("top_p"); topP.Exists() {
|
||||
out, _ = sjson.Set(out, "top_p", topP.Float())
|
||||
}
|
||||
|
||||
// Stop sequences -> stop
|
||||
if stopSequences := root.Get("stop_sequences"); stopSequences.Exists() {
|
||||
if stopSequences.IsArray() {
|
||||
var stops []string
|
||||
stopSequences.ForEach(func(_, value gjson.Result) bool {
|
||||
stops = append(stops, value.String())
|
||||
return true
|
||||
})
|
||||
if len(stops) > 0 {
|
||||
if len(stops) == 1 {
|
||||
out, _ = sjson.Set(out, "stop", stops[0])
|
||||
} else {
|
||||
out, _ = sjson.Set(out, "stop", stops)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stream
|
||||
if stream := root.Get("stream"); stream.Exists() {
|
||||
out, _ = sjson.Set(out, "stream", stream.Bool())
|
||||
}
|
||||
|
||||
// Process messages and system
|
||||
var openAIMessages []interface{}
|
||||
|
||||
// Handle system message first
|
||||
if system := root.Get("system"); system.Exists() && system.String() != "" {
|
||||
systemMsg := map[string]interface{}{
|
||||
"role": "system",
|
||||
"content": system.String(),
|
||||
}
|
||||
openAIMessages = append(openAIMessages, systemMsg)
|
||||
}
|
||||
|
||||
// Process Anthropic messages
|
||||
if messages := root.Get("messages"); messages.Exists() && messages.IsArray() {
|
||||
messages.ForEach(func(_, message gjson.Result) bool {
|
||||
role := message.Get("role").String()
|
||||
contentResult := message.Get("content")
|
||||
|
||||
msg := map[string]interface{}{
|
||||
"role": role,
|
||||
}
|
||||
|
||||
// Handle content
|
||||
if contentResult.Exists() && contentResult.IsArray() {
|
||||
var textParts []string
|
||||
var toolCalls []interface{}
|
||||
var toolResults []interface{}
|
||||
|
||||
contentResult.ForEach(func(_, part gjson.Result) bool {
|
||||
partType := part.Get("type").String()
|
||||
|
||||
switch partType {
|
||||
case "text":
|
||||
textParts = append(textParts, part.Get("text").String())
|
||||
|
||||
case "image":
|
||||
// Convert Anthropic image format to OpenAI format
|
||||
if source := part.Get("source"); source.Exists() {
|
||||
sourceType := source.Get("type").String()
|
||||
if sourceType == "base64" {
|
||||
mediaType := source.Get("media_type").String()
|
||||
data := source.Get("data").String()
|
||||
imageURL := "data:" + mediaType + ";base64," + data
|
||||
|
||||
// For now, add as text since OpenAI image handling is complex
|
||||
// In a real implementation, you'd need to handle this properly
|
||||
textParts = append(textParts, "[Image: "+imageURL+"]")
|
||||
}
|
||||
}
|
||||
|
||||
case "tool_use":
|
||||
// Convert to OpenAI tool call format
|
||||
toolCall := map[string]interface{}{
|
||||
"id": part.Get("id").String(),
|
||||
"type": "function",
|
||||
"function": map[string]interface{}{
|
||||
"name": part.Get("name").String(),
|
||||
},
|
||||
}
|
||||
|
||||
// Convert input to arguments JSON string
|
||||
if input := part.Get("input"); input.Exists() {
|
||||
if inputJSON, err := json.Marshal(input.Value()); err == nil {
|
||||
if function, ok := toolCall["function"].(map[string]interface{}); ok {
|
||||
function["arguments"] = string(inputJSON)
|
||||
}
|
||||
} else {
|
||||
if function, ok := toolCall["function"].(map[string]interface{}); ok {
|
||||
function["arguments"] = "{}"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if function, ok := toolCall["function"].(map[string]interface{}); ok {
|
||||
function["arguments"] = "{}"
|
||||
}
|
||||
}
|
||||
|
||||
toolCalls = append(toolCalls, toolCall)
|
||||
|
||||
case "tool_result":
|
||||
// Convert to OpenAI tool message format
|
||||
toolResult := map[string]interface{}{
|
||||
"role": "tool",
|
||||
"tool_call_id": part.Get("tool_use_id").String(),
|
||||
"content": part.Get("content").String(),
|
||||
}
|
||||
toolResults = append(toolResults, toolResult)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Set content
|
||||
if len(textParts) > 0 {
|
||||
msg["content"] = strings.Join(textParts, "")
|
||||
} else {
|
||||
msg["content"] = ""
|
||||
}
|
||||
|
||||
// Set tool calls for assistant messages
|
||||
if role == "assistant" && len(toolCalls) > 0 {
|
||||
msg["tool_calls"] = toolCalls
|
||||
}
|
||||
|
||||
openAIMessages = append(openAIMessages, msg)
|
||||
|
||||
// Add tool result messages separately
|
||||
for _, toolResult := range toolResults {
|
||||
openAIMessages = append(openAIMessages, toolResult)
|
||||
}
|
||||
|
||||
} else if contentResult.Exists() && contentResult.Type == gjson.String {
|
||||
// Simple string content
|
||||
msg["content"] = contentResult.String()
|
||||
openAIMessages = append(openAIMessages, msg)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Set messages
|
||||
if len(openAIMessages) > 0 {
|
||||
messagesJSON, _ := json.Marshal(openAIMessages)
|
||||
out, _ = sjson.SetRaw(out, "messages", string(messagesJSON))
|
||||
}
|
||||
|
||||
// Process tools - convert Anthropic tools to OpenAI functions
|
||||
if tools := root.Get("tools"); tools.Exists() && tools.IsArray() {
|
||||
var openAITools []interface{}
|
||||
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
openAITool := map[string]interface{}{
|
||||
"type": "function",
|
||||
"function": map[string]interface{}{
|
||||
"name": tool.Get("name").String(),
|
||||
"description": tool.Get("description").String(),
|
||||
},
|
||||
}
|
||||
|
||||
// Convert Anthropic input_schema to OpenAI function parameters
|
||||
if inputSchema := tool.Get("input_schema"); inputSchema.Exists() {
|
||||
if function, ok := openAITool["function"].(map[string]interface{}); ok {
|
||||
function["parameters"] = inputSchema.Value()
|
||||
}
|
||||
}
|
||||
|
||||
openAITools = append(openAITools, openAITool)
|
||||
return true
|
||||
})
|
||||
|
||||
if len(openAITools) > 0 {
|
||||
toolsJSON, _ := json.Marshal(openAITools)
|
||||
out, _ = sjson.SetRaw(out, "tools", string(toolsJSON))
|
||||
}
|
||||
}
|
||||
|
||||
// Tool choice mapping - convert Anthropic tool_choice to OpenAI format
|
||||
if toolChoice := root.Get("tool_choice"); toolChoice.Exists() {
|
||||
switch toolChoice.Get("type").String() {
|
||||
case "auto":
|
||||
out, _ = sjson.Set(out, "tool_choice", "auto")
|
||||
case "any":
|
||||
out, _ = sjson.Set(out, "tool_choice", "required")
|
||||
case "tool":
|
||||
// Specific tool choice
|
||||
toolName := toolChoice.Get("name").String()
|
||||
out, _ = sjson.Set(out, "tool_choice", map[string]interface{}{
|
||||
"type": "function",
|
||||
"function": map[string]interface{}{
|
||||
"name": toolName,
|
||||
},
|
||||
})
|
||||
default:
|
||||
// Default to auto if not specified
|
||||
out, _ = sjson.Set(out, "tool_choice", "auto")
|
||||
}
|
||||
}
|
||||
|
||||
// Handle user parameter (for tracking)
|
||||
if user := root.Get("user"); user.Exists() {
|
||||
out, _ = sjson.Set(out, "user", user.String())
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user