mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
357 lines
12 KiB
Go
357 lines
12 KiB
Go
// Package gemini provides request translation functionality for Gemini to OpenAI API.
|
|
// It handles parsing and transforming Gemini API requests into OpenAI Chat Completions API format,
|
|
// extracting model information, generation config, message contents, and tool declarations.
|
|
// The package performs JSON data transformation to ensure compatibility
|
|
// between Gemini API format and OpenAI API's expected format.
|
|
package gemini
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"math/big"
|
|
"strings"
|
|
|
|
"github.com/tidwall/gjson"
|
|
"github.com/tidwall/sjson"
|
|
)
|
|
|
|
// ConvertGeminiRequestToOpenAI parses and transforms a Gemini API request into OpenAI Chat Completions API format.
|
|
// It extracts the model name, generation config, message contents, and tool declarations
|
|
// from the raw JSON request and returns them in the format expected by the OpenAI API.
|
|
func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream bool) []byte {
|
|
rawJSON := bytes.Clone(inputRawJSON)
|
|
// Base OpenAI Chat Completions API template
|
|
out := `{"model":"","messages":[]}`
|
|
|
|
root := gjson.ParseBytes(rawJSON)
|
|
|
|
// Helper for generating tool call IDs in the form: call_<alphanum>
|
|
genToolCallID := func() string {
|
|
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
var b strings.Builder
|
|
// 24 chars random suffix
|
|
for i := 0; i < 24; i++ {
|
|
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
|
b.WriteByte(letters[n.Int64()])
|
|
}
|
|
return "call_" + b.String()
|
|
}
|
|
|
|
// Model mapping
|
|
out, _ = sjson.Set(out, "model", modelName)
|
|
|
|
// Generation config mapping
|
|
if genConfig := root.Get("generationConfig"); genConfig.Exists() {
|
|
// Temperature
|
|
if temp := genConfig.Get("temperature"); temp.Exists() {
|
|
out, _ = sjson.Set(out, "temperature", temp.Float())
|
|
}
|
|
|
|
// Max tokens
|
|
if maxTokens := genConfig.Get("maxOutputTokens"); maxTokens.Exists() {
|
|
out, _ = sjson.Set(out, "max_tokens", maxTokens.Int())
|
|
}
|
|
|
|
// Top P
|
|
if topP := genConfig.Get("topP"); topP.Exists() {
|
|
out, _ = sjson.Set(out, "top_p", topP.Float())
|
|
}
|
|
|
|
// Top K (OpenAI doesn't have direct equivalent, but we can map it)
|
|
if topK := genConfig.Get("topK"); topK.Exists() {
|
|
// Store as custom parameter for potential use
|
|
out, _ = sjson.Set(out, "top_k", topK.Int())
|
|
}
|
|
|
|
// Stop sequences
|
|
if stopSequences := genConfig.Get("stopSequences"); stopSequences.Exists() && stopSequences.IsArray() {
|
|
var stops []string
|
|
stopSequences.ForEach(func(_, value gjson.Result) bool {
|
|
stops = append(stops, value.String())
|
|
return true
|
|
})
|
|
if len(stops) > 0 {
|
|
out, _ = sjson.Set(out, "stop", stops)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stream parameter
|
|
out, _ = sjson.Set(out, "stream", stream)
|
|
|
|
// Process contents (Gemini messages) -> OpenAI messages
|
|
var openAIMessages []interface{}
|
|
var toolCallIDs []string // Track tool call IDs for matching with tool results
|
|
|
|
if contents := root.Get("contents"); contents.Exists() && contents.IsArray() {
|
|
contents.ForEach(func(_, content gjson.Result) bool {
|
|
role := content.Get("role").String()
|
|
parts := content.Get("parts")
|
|
|
|
// Convert role: model -> assistant
|
|
if role == "model" {
|
|
role = "assistant"
|
|
}
|
|
|
|
// Create OpenAI message
|
|
msg := map[string]interface{}{
|
|
"role": role,
|
|
"content": "",
|
|
}
|
|
|
|
var contentParts []string
|
|
var toolCalls []interface{}
|
|
|
|
if parts.Exists() && parts.IsArray() {
|
|
parts.ForEach(func(_, part gjson.Result) bool {
|
|
// Handle text parts
|
|
if text := part.Get("text"); text.Exists() {
|
|
contentParts = append(contentParts, text.String())
|
|
}
|
|
|
|
// Handle function calls (Gemini) -> tool calls (OpenAI)
|
|
if functionCall := part.Get("functionCall"); functionCall.Exists() {
|
|
toolCallID := genToolCallID()
|
|
toolCallIDs = append(toolCallIDs, toolCallID)
|
|
|
|
toolCall := map[string]interface{}{
|
|
"id": toolCallID,
|
|
"type": "function",
|
|
"function": map[string]interface{}{
|
|
"name": functionCall.Get("name").String(),
|
|
},
|
|
}
|
|
|
|
// Convert args to arguments JSON string
|
|
if args := functionCall.Get("args"); args.Exists() {
|
|
argsJSON, _ := json.Marshal(args.Value())
|
|
toolCall["function"].(map[string]interface{})["arguments"] = string(argsJSON)
|
|
} else {
|
|
toolCall["function"].(map[string]interface{})["arguments"] = "{}"
|
|
}
|
|
|
|
toolCalls = append(toolCalls, toolCall)
|
|
}
|
|
|
|
// Handle function responses (Gemini) -> tool role messages (OpenAI)
|
|
if functionResponse := part.Get("functionResponse"); functionResponse.Exists() {
|
|
// Create tool message for function response
|
|
toolMsg := map[string]interface{}{
|
|
"role": "tool",
|
|
"tool_call_id": "", // Will be set based on context
|
|
"content": "",
|
|
}
|
|
|
|
// Convert response.content to JSON string
|
|
if response := functionResponse.Get("response"); response.Exists() {
|
|
if content = response.Get("content"); content.Exists() {
|
|
// Use the content field from the response
|
|
contentJSON, _ := json.Marshal(content.Value())
|
|
toolMsg["content"] = string(contentJSON)
|
|
} else {
|
|
// Fallback to entire response
|
|
responseJSON, _ := json.Marshal(response.Value())
|
|
toolMsg["content"] = string(responseJSON)
|
|
}
|
|
}
|
|
|
|
// Try to match with previous tool call ID
|
|
_ = functionResponse.Get("name").String() // functionName not used for now
|
|
if len(toolCallIDs) > 0 {
|
|
// Use the last tool call ID (simple matching by function name)
|
|
// In a real implementation, you might want more sophisticated matching
|
|
toolMsg["tool_call_id"] = toolCallIDs[len(toolCallIDs)-1]
|
|
} else {
|
|
// Generate a tool call ID if none available
|
|
toolMsg["tool_call_id"] = genToolCallID()
|
|
}
|
|
|
|
openAIMessages = append(openAIMessages, toolMsg)
|
|
}
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
// Set content
|
|
if len(contentParts) > 0 {
|
|
msg["content"] = strings.Join(contentParts, "")
|
|
}
|
|
|
|
// Set tool calls if any
|
|
if len(toolCalls) > 0 {
|
|
msg["tool_calls"] = toolCalls
|
|
}
|
|
|
|
openAIMessages = append(openAIMessages, msg)
|
|
|
|
// switch role {
|
|
// case "user", "model":
|
|
// // Convert role: model -> assistant
|
|
// if role == "model" {
|
|
// role = "assistant"
|
|
// }
|
|
//
|
|
// // Create OpenAI message
|
|
// msg := map[string]interface{}{
|
|
// "role": role,
|
|
// "content": "",
|
|
// }
|
|
//
|
|
// var contentParts []string
|
|
// var toolCalls []interface{}
|
|
//
|
|
// if parts.Exists() && parts.IsArray() {
|
|
// parts.ForEach(func(_, part gjson.Result) bool {
|
|
// // Handle text parts
|
|
// if text := part.Get("text"); text.Exists() {
|
|
// contentParts = append(contentParts, text.String())
|
|
// }
|
|
//
|
|
// // Handle function calls (Gemini) -> tool calls (OpenAI)
|
|
// if functionCall := part.Get("functionCall"); functionCall.Exists() {
|
|
// toolCallID := genToolCallID()
|
|
// toolCallIDs = append(toolCallIDs, toolCallID)
|
|
//
|
|
// toolCall := map[string]interface{}{
|
|
// "id": toolCallID,
|
|
// "type": "function",
|
|
// "function": map[string]interface{}{
|
|
// "name": functionCall.Get("name").String(),
|
|
// },
|
|
// }
|
|
//
|
|
// // Convert args to arguments JSON string
|
|
// if args := functionCall.Get("args"); args.Exists() {
|
|
// argsJSON, _ := json.Marshal(args.Value())
|
|
// toolCall["function"].(map[string]interface{})["arguments"] = string(argsJSON)
|
|
// } else {
|
|
// toolCall["function"].(map[string]interface{})["arguments"] = "{}"
|
|
// }
|
|
//
|
|
// toolCalls = append(toolCalls, toolCall)
|
|
// }
|
|
//
|
|
// return true
|
|
// })
|
|
// }
|
|
//
|
|
// // Set content
|
|
// if len(contentParts) > 0 {
|
|
// msg["content"] = strings.Join(contentParts, "")
|
|
// }
|
|
//
|
|
// // Set tool calls if any
|
|
// if len(toolCalls) > 0 {
|
|
// msg["tool_calls"] = toolCalls
|
|
// }
|
|
//
|
|
// openAIMessages = append(openAIMessages, msg)
|
|
//
|
|
// case "function":
|
|
// // Handle Gemini function role -> OpenAI tool role
|
|
// if parts.Exists() && parts.IsArray() {
|
|
// parts.ForEach(func(_, part gjson.Result) bool {
|
|
// // Handle function responses (Gemini) -> tool role messages (OpenAI)
|
|
// if functionResponse := part.Get("functionResponse"); functionResponse.Exists() {
|
|
// // Create tool message for function response
|
|
// toolMsg := map[string]interface{}{
|
|
// "role": "tool",
|
|
// "tool_call_id": "", // Will be set based on context
|
|
// "content": "",
|
|
// }
|
|
//
|
|
// // Convert response.content to JSON string
|
|
// if response := functionResponse.Get("response"); response.Exists() {
|
|
// if content = response.Get("content"); content.Exists() {
|
|
// // Use the content field from the response
|
|
// contentJSON, _ := json.Marshal(content.Value())
|
|
// toolMsg["content"] = string(contentJSON)
|
|
// } else {
|
|
// // Fallback to entire response
|
|
// responseJSON, _ := json.Marshal(response.Value())
|
|
// toolMsg["content"] = string(responseJSON)
|
|
// }
|
|
// }
|
|
//
|
|
// // Try to match with previous tool call ID
|
|
// _ = functionResponse.Get("name").String() // functionName not used for now
|
|
// if len(toolCallIDs) > 0 {
|
|
// // Use the last tool call ID (simple matching by function name)
|
|
// // In a real implementation, you might want more sophisticated matching
|
|
// toolMsg["tool_call_id"] = toolCallIDs[len(toolCallIDs)-1]
|
|
// } else {
|
|
// // Generate a tool call ID if none available
|
|
// toolMsg["tool_call_id"] = genToolCallID()
|
|
// }
|
|
//
|
|
// openAIMessages = append(openAIMessages, toolMsg)
|
|
// }
|
|
//
|
|
// return true
|
|
// })
|
|
// }
|
|
// }
|
|
return true
|
|
})
|
|
}
|
|
|
|
// Set messages
|
|
if len(openAIMessages) > 0 {
|
|
messagesJSON, _ := json.Marshal(openAIMessages)
|
|
out, _ = sjson.SetRaw(out, "messages", string(messagesJSON))
|
|
}
|
|
|
|
// Tools mapping: Gemini tools -> OpenAI tools
|
|
if tools := root.Get("tools"); tools.Exists() && tools.IsArray() {
|
|
var openAITools []interface{}
|
|
tools.ForEach(func(_, tool gjson.Result) bool {
|
|
if functionDeclarations := tool.Get("functionDeclarations"); functionDeclarations.Exists() && functionDeclarations.IsArray() {
|
|
functionDeclarations.ForEach(func(_, funcDecl gjson.Result) bool {
|
|
openAITool := map[string]interface{}{
|
|
"type": "function",
|
|
"function": map[string]interface{}{
|
|
"name": funcDecl.Get("name").String(),
|
|
"description": funcDecl.Get("description").String(),
|
|
},
|
|
}
|
|
|
|
// Convert parameters schema
|
|
if parameters := funcDecl.Get("parameters"); parameters.Exists() {
|
|
openAITool["function"].(map[string]interface{})["parameters"] = parameters.Value()
|
|
} else if parameters = funcDecl.Get("parametersJsonSchema"); parameters.Exists() {
|
|
openAITool["function"].(map[string]interface{})["parameters"] = parameters.Value()
|
|
}
|
|
|
|
openAITools = append(openAITools, openAITool)
|
|
return true
|
|
})
|
|
}
|
|
return true
|
|
})
|
|
|
|
if len(openAITools) > 0 {
|
|
toolsJSON, _ := json.Marshal(openAITools)
|
|
out, _ = sjson.SetRaw(out, "tools", string(toolsJSON))
|
|
}
|
|
}
|
|
|
|
// Tool choice mapping (Gemini doesn't have direct equivalent, but we can handle it)
|
|
if toolConfig := root.Get("toolConfig"); toolConfig.Exists() {
|
|
if functionCallingConfig := toolConfig.Get("functionCallingConfig"); functionCallingConfig.Exists() {
|
|
mode := functionCallingConfig.Get("mode").String()
|
|
switch mode {
|
|
case "NONE":
|
|
out, _ = sjson.Set(out, "tool_choice", "none")
|
|
case "AUTO":
|
|
out, _ = sjson.Set(out, "tool_choice", "auto")
|
|
case "ANY":
|
|
out, _ = sjson.Set(out, "tool_choice", "required")
|
|
}
|
|
}
|
|
}
|
|
|
|
return []byte(out)
|
|
}
|