refactor: replace json.Marshal and json.Unmarshal with sjson and gjson

Optimized the handling of JSON serialization and deserialization by replacing redundant `json.Marshal` and `json.Unmarshal` calls with `sjson` and `gjson`. Introduced a `marshalJSONValue` utility for compact JSON encoding, improving performance and code simplicity. Removed unused `encoding/json` imports.
This commit is contained in:
Luis Pater
2025-12-22 11:27:55 +08:00
parent dbcbe48ead
commit a86d501dc2
27 changed files with 862 additions and 1613 deletions

View File

@@ -8,7 +8,6 @@ package gemini
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"math/big"
"strings"
@@ -94,7 +93,6 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
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
// System instruction -> OpenAI system message
@@ -105,22 +103,17 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
}
if systemInstruction.Exists() {
parts := systemInstruction.Get("parts")
msg := map[string]interface{}{
"role": "system",
"content": []interface{}{},
}
var aggregatedParts []interface{}
msg := `{"role":"system","content":[]}`
hasContent := false
if parts.Exists() && parts.IsArray() {
parts.ForEach(func(_, part gjson.Result) bool {
// Handle text parts
if text := part.Get("text"); text.Exists() {
formattedText := text.String()
aggregatedParts = append(aggregatedParts, map[string]interface{}{
"type": "text",
"text": formattedText,
})
contentPart := `{"type":"text","text":""}`
contentPart, _ = sjson.Set(contentPart, "text", text.String())
msg, _ = sjson.SetRaw(msg, "content.-1", contentPart)
hasContent = true
}
// Handle inline data (e.g., images)
@@ -132,20 +125,17 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
data := inlineData.Get("data").String()
imageURL := fmt.Sprintf("data:%s;base64,%s", mimeType, data)
aggregatedParts = append(aggregatedParts, map[string]interface{}{
"type": "image_url",
"image_url": map[string]interface{}{
"url": imageURL,
},
})
contentPart := `{"type":"image_url","image_url":{"url":""}}`
contentPart, _ = sjson.Set(contentPart, "image_url.url", imageURL)
msg, _ = sjson.SetRaw(msg, "content.-1", contentPart)
hasContent = true
}
return true
})
}
if len(aggregatedParts) > 0 {
msg["content"] = aggregatedParts
openAIMessages = append(openAIMessages, msg)
if hasContent {
out, _ = sjson.SetRaw(out, "messages.-1", msg)
}
}
@@ -159,16 +149,15 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
role = "assistant"
}
// Create OpenAI message
msg := map[string]interface{}{
"role": role,
"content": "",
}
msg := `{"role":"","content":""}`
msg, _ = sjson.Set(msg, "role", role)
var textBuilder strings.Builder
var aggregatedParts []interface{}
contentWrapper := `{"arr":[]}`
contentPartsCount := 0
onlyTextContent := true
var toolCalls []interface{}
toolCallsWrapper := `{"arr":[]}`
toolCallsCount := 0
if parts.Exists() && parts.IsArray() {
parts.ForEach(func(_, part gjson.Result) bool {
@@ -176,10 +165,10 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
if text := part.Get("text"); text.Exists() {
formattedText := text.String()
textBuilder.WriteString(formattedText)
aggregatedParts = append(aggregatedParts, map[string]interface{}{
"type": "text",
"text": formattedText,
})
contentPart := `{"type":"text","text":""}`
contentPart, _ = sjson.Set(contentPart, "text", formattedText)
contentWrapper, _ = sjson.SetRaw(contentWrapper, "arr.-1", contentPart)
contentPartsCount++
}
// Handle inline data (e.g., images)
@@ -193,12 +182,10 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
data := inlineData.Get("data").String()
imageURL := fmt.Sprintf("data:%s;base64,%s", mimeType, data)
aggregatedParts = append(aggregatedParts, map[string]interface{}{
"type": "image_url",
"image_url": map[string]interface{}{
"url": imageURL,
},
})
contentPart := `{"type":"image_url","image_url":{"url":""}}`
contentPart, _ = sjson.Set(contentPart, "image_url.url", imageURL)
contentWrapper, _ = sjson.SetRaw(contentWrapper, "arr.-1", contentPart)
contentPartsCount++
}
// Handle function calls (Gemini) -> tool calls (OpenAI)
@@ -206,44 +193,32 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
toolCallID := genToolCallID()
toolCallIDs = append(toolCallIDs, toolCallID)
toolCall := map[string]interface{}{
"id": toolCallID,
"type": "function",
"function": map[string]interface{}{
"name": functionCall.Get("name").String(),
},
}
toolCall := `{"id":"","type":"function","function":{"name":"","arguments":""}}`
toolCall, _ = sjson.Set(toolCall, "id", toolCallID)
toolCall, _ = sjson.Set(toolCall, "function.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)
toolCall, _ = sjson.Set(toolCall, "function.arguments", args.Raw)
} else {
toolCall["function"].(map[string]interface{})["arguments"] = "{}"
toolCall, _ = sjson.Set(toolCall, "function.arguments", "{}")
}
toolCalls = append(toolCalls, toolCall)
toolCallsWrapper, _ = sjson.SetRaw(toolCallsWrapper, "arr.-1", toolCall)
toolCallsCount++
}
// 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": "",
}
toolMsg := `{"role":"tool","tool_call_id":"","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)
if contentField := response.Get("content"); contentField.Exists() {
toolMsg, _ = sjson.Set(toolMsg, "content", contentField.Raw)
} else {
// Fallback to entire response
responseJSON, _ := json.Marshal(response.Value())
toolMsg["content"] = string(responseJSON)
toolMsg, _ = sjson.Set(toolMsg, "content", response.Raw)
}
}
@@ -252,13 +227,13 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
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]
toolMsg, _ = sjson.Set(toolMsg, "tool_call_id", toolCallIDs[len(toolCallIDs)-1])
} else {
// Generate a tool call ID if none available
toolMsg["tool_call_id"] = genToolCallID()
toolMsg, _ = sjson.Set(toolMsg, "tool_call_id", genToolCallID())
}
openAIMessages = append(openAIMessages, toolMsg)
out, _ = sjson.SetRaw(out, "messages.-1", toolMsg)
}
return true
@@ -266,170 +241,46 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
}
// Set content
if len(aggregatedParts) > 0 {
if contentPartsCount > 0 {
if onlyTextContent {
msg["content"] = textBuilder.String()
msg, _ = sjson.Set(msg, "content", textBuilder.String())
} else {
msg["content"] = aggregatedParts
msg, _ = sjson.SetRaw(msg, "content", gjson.Get(contentWrapper, "arr").Raw)
}
}
// Set tool calls if any
if len(toolCalls) > 0 {
msg["tool_calls"] = toolCalls
if toolCallsCount > 0 {
msg, _ = sjson.SetRaw(msg, "tool_calls", gjson.Get(toolCallsWrapper, "arr").Raw)
}
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
// })
// }
// }
out, _ = sjson.SetRaw(out, "messages.-1", msg)
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(),
},
}
openAITool := `{"type":"function","function":{"name":"","description":""}}`
openAITool, _ = sjson.Set(openAITool, "function.name", funcDecl.Get("name").String())
openAITool, _ = sjson.Set(openAITool, "function.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()
openAITool, _ = sjson.SetRaw(openAITool, "function.parameters", parameters.Raw)
} else if parameters := funcDecl.Get("parametersJsonSchema"); parameters.Exists() {
openAITool, _ = sjson.SetRaw(openAITool, "function.parameters", parameters.Raw)
}
openAITools = append(openAITools, openAITool)
out, _ = sjson.SetRaw(out, "tools.-1", 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)

View File

@@ -8,7 +8,6 @@ package gemini
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
@@ -84,15 +83,12 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
template, _ = sjson.Set(template, "model", model.String())
}
usageObj := map[string]interface{}{
"promptTokenCount": usage.Get("prompt_tokens").Int(),
"candidatesTokenCount": usage.Get("completion_tokens").Int(),
"totalTokenCount": usage.Get("total_tokens").Int(),
}
template, _ = sjson.Set(template, "usageMetadata.promptTokenCount", usage.Get("prompt_tokens").Int())
template, _ = sjson.Set(template, "usageMetadata.candidatesTokenCount", usage.Get("completion_tokens").Int())
template, _ = sjson.Set(template, "usageMetadata.totalTokenCount", usage.Get("total_tokens").Int())
if reasoningTokens := reasoningTokensFromUsage(usage); reasoningTokens > 0 {
usageObj["thoughtsTokenCount"] = reasoningTokens
template, _ = sjson.Set(template, "usageMetadata.thoughtsTokenCount", reasoningTokens)
}
template, _ = sjson.Set(template, "usageMetadata", usageObj)
return []string{template}
}
return []string{}
@@ -133,13 +129,8 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
continue
}
reasoningTemplate := baseTemplate
parts := []interface{}{
map[string]interface{}{
"thought": true,
"text": reasoningText,
},
}
reasoningTemplate, _ = sjson.Set(reasoningTemplate, "candidates.0.content.parts", parts)
reasoningTemplate, _ = sjson.Set(reasoningTemplate, "candidates.0.content.parts.0.thought", true)
reasoningTemplate, _ = sjson.Set(reasoningTemplate, "candidates.0.content.parts.0.text", reasoningText)
chunkOutputs = append(chunkOutputs, reasoningTemplate)
}
}
@@ -150,13 +141,8 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
(*param).(*ConvertOpenAIResponseToGeminiParams).ContentAccumulator.WriteString(contentText)
// Create text part for this delta
parts := []interface{}{
map[string]interface{}{
"text": contentText,
},
}
contentTemplate := baseTemplate
contentTemplate, _ = sjson.Set(contentTemplate, "candidates.0.content.parts", parts)
contentTemplate, _ = sjson.Set(contentTemplate, "candidates.0.content.parts.0.text", contentText)
chunkOutputs = append(chunkOutputs, contentTemplate)
}
@@ -225,24 +211,13 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
// If we have accumulated tool calls, output them now
if len((*param).(*ConvertOpenAIResponseToGeminiParams).ToolCallsAccumulator) > 0 {
var parts []interface{}
partIndex := 0
for _, accumulator := range (*param).(*ConvertOpenAIResponseToGeminiParams).ToolCallsAccumulator {
argsStr := accumulator.Arguments.String()
var argsMap map[string]interface{}
argsMap = parseArgsToMap(argsStr)
functionCallPart := map[string]interface{}{
"functionCall": map[string]interface{}{
"name": accumulator.Name,
"args": argsMap,
},
}
parts = append(parts, functionCallPart)
}
if len(parts) > 0 {
template, _ = sjson.Set(template, "candidates.0.content.parts", parts)
namePath := fmt.Sprintf("candidates.0.content.parts.%d.functionCall.name", partIndex)
argsPath := fmt.Sprintf("candidates.0.content.parts.%d.functionCall.args", partIndex)
template, _ = sjson.Set(template, namePath, accumulator.Name)
template, _ = sjson.SetRaw(template, argsPath, parseArgsToObjectRaw(accumulator.Arguments.String()))
partIndex++
}
// Clear accumulators
@@ -255,15 +230,12 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
// Handle usage information
if usage := root.Get("usage"); usage.Exists() {
usageObj := map[string]interface{}{
"promptTokenCount": usage.Get("prompt_tokens").Int(),
"candidatesTokenCount": usage.Get("completion_tokens").Int(),
"totalTokenCount": usage.Get("total_tokens").Int(),
}
template, _ = sjson.Set(template, "usageMetadata.promptTokenCount", usage.Get("prompt_tokens").Int())
template, _ = sjson.Set(template, "usageMetadata.candidatesTokenCount", usage.Get("completion_tokens").Int())
template, _ = sjson.Set(template, "usageMetadata.totalTokenCount", usage.Get("total_tokens").Int())
if reasoningTokens := reasoningTokensFromUsage(usage); reasoningTokens > 0 {
usageObj["thoughtsTokenCount"] = reasoningTokens
template, _ = sjson.Set(template, "usageMetadata.thoughtsTokenCount", reasoningTokens)
}
template, _ = sjson.Set(template, "usageMetadata", usageObj)
results = append(results, template)
return true
}
@@ -291,46 +263,54 @@ func mapOpenAIFinishReasonToGemini(openAIReason string) string {
}
}
// parseArgsToMap safely parses a JSON string of function arguments into a map.
// It returns an empty map if the input is empty or cannot be parsed as a JSON object.
func parseArgsToMap(argsStr string) map[string]interface{} {
// parseArgsToObjectRaw safely parses a JSON string of function arguments into an object JSON string.
// It returns "{}" if the input is empty or cannot be parsed as a JSON object.
func parseArgsToObjectRaw(argsStr string) string {
trimmed := strings.TrimSpace(argsStr)
if trimmed == "" || trimmed == "{}" {
return map[string]interface{}{}
return "{}"
}
// First try strict JSON
var out map[string]interface{}
if errUnmarshal := json.Unmarshal([]byte(trimmed), &out); errUnmarshal == nil {
return out
if gjson.Valid(trimmed) {
strict := gjson.Parse(trimmed)
if strict.IsObject() {
return strict.Raw
}
}
// Tolerant parse: handle streams where values are barewords (e.g., 北京, celsius)
tolerant := tolerantParseJSONMap(trimmed)
if len(tolerant) > 0 {
tolerant := tolerantParseJSONObjectRaw(trimmed)
if tolerant != "{}" {
return tolerant
}
// Fallback: return empty object when parsing fails
return map[string]interface{}{}
return "{}"
}
// tolerantParseJSONMap attempts to parse a JSON-like object string into a map, tolerating
func escapeSjsonPathKey(key string) string {
key = strings.ReplaceAll(key, `\`, `\\`)
key = strings.ReplaceAll(key, `.`, `\.`)
return key
}
// tolerantParseJSONObjectRaw attempts to parse a JSON-like object string into a JSON object string, tolerating
// bareword values (unquoted strings) commonly seen during streamed tool calls.
// Example input: {"location": 北京, "unit": celsius}
func tolerantParseJSONMap(s string) map[string]interface{} {
func tolerantParseJSONObjectRaw(s string) string {
// Ensure we operate within the outermost braces if present
start := strings.Index(s, "{")
end := strings.LastIndex(s, "}")
if start == -1 || end == -1 || start >= end {
return map[string]interface{}{}
return "{}"
}
content := s[start+1 : end]
runes := []rune(content)
n := len(runes)
i := 0
result := make(map[string]interface{})
result := "{}"
for i < n {
// Skip whitespace and commas
@@ -356,6 +336,7 @@ func tolerantParseJSONMap(s string) map[string]interface{} {
break
}
keyName := jsonStringTokenToRawString(keyToken)
sjsonKey := escapeSjsonPathKey(keyName)
i = nextIdx
// Skip whitespace
@@ -375,17 +356,16 @@ func tolerantParseJSONMap(s string) map[string]interface{} {
}
// Parse value (string, number, object/array, bareword)
var value interface{}
switch runes[i] {
case '"':
// JSON string
valToken, ni := parseJSONStringRunes(runes, i)
if ni == -1 {
// Malformed; treat as empty string
value = ""
result, _ = sjson.Set(result, sjsonKey, "")
i = n
} else {
value = jsonStringTokenToRawString(valToken)
result, _ = sjson.Set(result, sjsonKey, jsonStringTokenToRawString(valToken))
i = ni
}
case '{', '[':
@@ -394,11 +374,10 @@ func tolerantParseJSONMap(s string) map[string]interface{} {
if ni == -1 {
i = n
} else {
var anyVal interface{}
if errUnmarshal := json.Unmarshal([]byte(seg), &anyVal); errUnmarshal == nil {
value = anyVal
if gjson.Valid(seg) {
result, _ = sjson.SetRaw(result, sjsonKey, seg)
} else {
value = seg
result, _ = sjson.Set(result, sjsonKey, seg)
}
i = ni
}
@@ -411,21 +390,19 @@ func tolerantParseJSONMap(s string) map[string]interface{} {
token := strings.TrimSpace(string(runes[i:j]))
// Interpret common JSON atoms and numbers; otherwise treat as string
if token == "true" {
value = true
result, _ = sjson.Set(result, sjsonKey, true)
} else if token == "false" {
value = false
result, _ = sjson.Set(result, sjsonKey, false)
} else if token == "null" {
value = nil
result, _ = sjson.Set(result, sjsonKey, nil)
} else if numVal, ok := tryParseNumber(token); ok {
value = numVal
result, _ = sjson.Set(result, sjsonKey, numVal)
} else {
value = token
result, _ = sjson.Set(result, sjsonKey, token)
}
i = j
}
result[keyName] = value
// Skip trailing whitespace and optional comma before next pair
for i < n && (runes[i] == ' ' || runes[i] == '\n' || runes[i] == '\r' || runes[i] == '\t') {
i++
@@ -463,9 +440,9 @@ func parseJSONStringRunes(runes []rune, start int) (string, int) {
// jsonStringTokenToRawString converts a JSON string token (including quotes) to a raw Go string value.
func jsonStringTokenToRawString(token string) string {
var s string
if errUnmarshal := json.Unmarshal([]byte(token), &s); errUnmarshal == nil {
return s
r := gjson.Parse(token)
if r.Type == gjson.String {
return r.String()
}
// Fallback: strip surrounding quotes if present
if len(token) >= 2 && token[0] == '"' && token[len(token)-1] == '"' {
@@ -579,7 +556,7 @@ func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, origina
}
}
var parts []interface{}
partIndex := 0
// Handle reasoning content before visible text
if reasoning := message.Get("reasoning_content"); reasoning.Exists() {
@@ -587,18 +564,16 @@ func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, origina
if reasoningText == "" {
continue
}
parts = append(parts, map[string]interface{}{
"thought": true,
"text": reasoningText,
})
out, _ = sjson.Set(out, fmt.Sprintf("candidates.0.content.parts.%d.thought", partIndex), true)
out, _ = sjson.Set(out, fmt.Sprintf("candidates.0.content.parts.%d.text", partIndex), reasoningText)
partIndex++
}
}
// Handle content first
if content := message.Get("content"); content.Exists() && content.String() != "" {
parts = append(parts, map[string]interface{}{
"text": content.String(),
})
out, _ = sjson.Set(out, fmt.Sprintf("candidates.0.content.parts.%d.text", partIndex), content.String())
partIndex++
}
// Handle tool calls
@@ -609,27 +584,16 @@ func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, origina
functionName := function.Get("name").String()
functionArgs := function.Get("arguments").String()
// Parse arguments
var argsMap map[string]interface{}
argsMap = parseArgsToMap(functionArgs)
functionCallPart := map[string]interface{}{
"functionCall": map[string]interface{}{
"name": functionName,
"args": argsMap,
},
}
parts = append(parts, functionCallPart)
namePath := fmt.Sprintf("candidates.0.content.parts.%d.functionCall.name", partIndex)
argsPath := fmt.Sprintf("candidates.0.content.parts.%d.functionCall.args", partIndex)
out, _ = sjson.Set(out, namePath, functionName)
out, _ = sjson.SetRaw(out, argsPath, parseArgsToObjectRaw(functionArgs))
partIndex++
}
return true
})
}
// Set parts
if len(parts) > 0 {
out, _ = sjson.Set(out, "candidates.0.content.parts", parts)
}
// Handle finish reason
if finishReason := choice.Get("finish_reason"); finishReason.Exists() {
geminiFinishReason := mapOpenAIFinishReasonToGemini(finishReason.String())
@@ -645,15 +609,12 @@ func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, origina
// Handle usage information
if usage := root.Get("usage"); usage.Exists() {
usageObj := map[string]interface{}{
"promptTokenCount": usage.Get("prompt_tokens").Int(),
"candidatesTokenCount": usage.Get("completion_tokens").Int(),
"totalTokenCount": usage.Get("total_tokens").Int(),
}
out, _ = sjson.Set(out, "usageMetadata.promptTokenCount", usage.Get("prompt_tokens").Int())
out, _ = sjson.Set(out, "usageMetadata.candidatesTokenCount", usage.Get("completion_tokens").Int())
out, _ = sjson.Set(out, "usageMetadata.totalTokenCount", usage.Get("total_tokens").Int())
if reasoningTokens := reasoningTokensFromUsage(usage); reasoningTokens > 0 {
usageObj["thoughtsTokenCount"] = reasoningTokens
out, _ = sjson.Set(out, "usageMetadata.thoughtsTokenCount", reasoningTokens)
}
out, _ = sjson.Set(out, "usageMetadata", usageObj)
}
return out