refactor: replace sjson.Set usage with sjson.SetBytes to optimize mutable JSON transformations
This commit is contained in:
@@ -22,7 +22,7 @@ import (
|
||||
func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream bool) []byte {
|
||||
rawJSON := inputRawJSON
|
||||
// Base OpenAI Chat Completions API template
|
||||
out := `{"model":"","messages":[]}`
|
||||
out := []byte(`{"model":"","messages":[]}`)
|
||||
|
||||
root := gjson.ParseBytes(rawJSON)
|
||||
|
||||
@@ -39,29 +39,29 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
}
|
||||
|
||||
// Model mapping
|
||||
out, _ = sjson.Set(out, "model", modelName)
|
||||
out, _ = sjson.SetBytes(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())
|
||||
out, _ = sjson.SetBytes(out, "temperature", temp.Float())
|
||||
}
|
||||
|
||||
// Max tokens
|
||||
if maxTokens := genConfig.Get("maxOutputTokens"); maxTokens.Exists() {
|
||||
out, _ = sjson.Set(out, "max_tokens", maxTokens.Int())
|
||||
out, _ = sjson.SetBytes(out, "max_tokens", maxTokens.Int())
|
||||
}
|
||||
|
||||
// Top P
|
||||
if topP := genConfig.Get("topP"); topP.Exists() {
|
||||
out, _ = sjson.Set(out, "top_p", topP.Float())
|
||||
out, _ = sjson.SetBytes(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())
|
||||
out, _ = sjson.SetBytes(out, "top_k", topK.Int())
|
||||
}
|
||||
|
||||
// Stop sequences
|
||||
@@ -72,13 +72,13 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
return true
|
||||
})
|
||||
if len(stops) > 0 {
|
||||
out, _ = sjson.Set(out, "stop", stops)
|
||||
out, _ = sjson.SetBytes(out, "stop", stops)
|
||||
}
|
||||
}
|
||||
|
||||
// Candidate count (OpenAI 'n' parameter)
|
||||
if candidateCount := genConfig.Get("candidateCount"); candidateCount.Exists() {
|
||||
out, _ = sjson.Set(out, "n", candidateCount.Int())
|
||||
out, _ = sjson.SetBytes(out, "n", candidateCount.Int())
|
||||
}
|
||||
|
||||
// Map Gemini thinkingConfig to OpenAI reasoning_effort.
|
||||
@@ -92,7 +92,7 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
if thinkingLevel.Exists() {
|
||||
effort := strings.ToLower(strings.TrimSpace(thinkingLevel.String()))
|
||||
if effort != "" {
|
||||
out, _ = sjson.Set(out, "reasoning_effort", effort)
|
||||
out, _ = sjson.SetBytes(out, "reasoning_effort", effort)
|
||||
}
|
||||
} else {
|
||||
thinkingBudget := thinkingConfig.Get("thinkingBudget")
|
||||
@@ -101,7 +101,7 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
}
|
||||
if thinkingBudget.Exists() {
|
||||
if effort, ok := thinking.ConvertBudgetToLevel(int(thinkingBudget.Int())); ok {
|
||||
out, _ = sjson.Set(out, "reasoning_effort", effort)
|
||||
out, _ = sjson.SetBytes(out, "reasoning_effort", effort)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
}
|
||||
|
||||
// Stream parameter
|
||||
out, _ = sjson.Set(out, "stream", stream)
|
||||
out, _ = sjson.SetBytes(out, "stream", stream)
|
||||
|
||||
// Process contents (Gemini messages) -> OpenAI messages
|
||||
var toolCallIDs []string // Track tool call IDs for matching with tool results
|
||||
@@ -122,16 +122,16 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
}
|
||||
if systemInstruction.Exists() {
|
||||
parts := systemInstruction.Get("parts")
|
||||
msg := `{"role":"system","content":[]}`
|
||||
msg := []byte(`{"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() {
|
||||
contentPart := `{"type":"text","text":""}`
|
||||
contentPart, _ = sjson.Set(contentPart, "text", text.String())
|
||||
msg, _ = sjson.SetRaw(msg, "content.-1", contentPart)
|
||||
contentPart := []byte(`{"type":"text","text":""}`)
|
||||
contentPart, _ = sjson.SetBytes(contentPart, "text", text.String())
|
||||
msg, _ = sjson.SetRawBytes(msg, "content.-1", contentPart)
|
||||
hasContent = true
|
||||
}
|
||||
|
||||
@@ -144,9 +144,9 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
data := inlineData.Get("data").String()
|
||||
imageURL := fmt.Sprintf("data:%s;base64,%s", mimeType, data)
|
||||
|
||||
contentPart := `{"type":"image_url","image_url":{"url":""}}`
|
||||
contentPart, _ = sjson.Set(contentPart, "image_url.url", imageURL)
|
||||
msg, _ = sjson.SetRaw(msg, "content.-1", contentPart)
|
||||
contentPart := []byte(`{"type":"image_url","image_url":{"url":""}}`)
|
||||
contentPart, _ = sjson.SetBytes(contentPart, "image_url.url", imageURL)
|
||||
msg, _ = sjson.SetRawBytes(msg, "content.-1", contentPart)
|
||||
hasContent = true
|
||||
}
|
||||
return true
|
||||
@@ -154,7 +154,7 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
}
|
||||
|
||||
if hasContent {
|
||||
out, _ = sjson.SetRaw(out, "messages.-1", msg)
|
||||
out, _ = sjson.SetRawBytes(out, "messages.-1", msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,14 +168,14 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
role = "assistant"
|
||||
}
|
||||
|
||||
msg := `{"role":"","content":""}`
|
||||
msg, _ = sjson.Set(msg, "role", role)
|
||||
msg := []byte(`{"role":"","content":""}`)
|
||||
msg, _ = sjson.SetBytes(msg, "role", role)
|
||||
|
||||
var textBuilder strings.Builder
|
||||
contentWrapper := `{"arr":[]}`
|
||||
contentWrapper := []byte(`{"arr":[]}`)
|
||||
contentPartsCount := 0
|
||||
onlyTextContent := true
|
||||
toolCallsWrapper := `{"arr":[]}`
|
||||
toolCallsWrapper := []byte(`{"arr":[]}`)
|
||||
toolCallsCount := 0
|
||||
|
||||
if parts.Exists() && parts.IsArray() {
|
||||
@@ -184,9 +184,9 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
if text := part.Get("text"); text.Exists() {
|
||||
formattedText := text.String()
|
||||
textBuilder.WriteString(formattedText)
|
||||
contentPart := `{"type":"text","text":""}`
|
||||
contentPart, _ = sjson.Set(contentPart, "text", formattedText)
|
||||
contentWrapper, _ = sjson.SetRaw(contentWrapper, "arr.-1", contentPart)
|
||||
contentPart := []byte(`{"type":"text","text":""}`)
|
||||
contentPart, _ = sjson.SetBytes(contentPart, "text", formattedText)
|
||||
contentWrapper, _ = sjson.SetRawBytes(contentWrapper, "arr.-1", contentPart)
|
||||
contentPartsCount++
|
||||
}
|
||||
|
||||
@@ -201,9 +201,9 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
data := inlineData.Get("data").String()
|
||||
imageURL := fmt.Sprintf("data:%s;base64,%s", mimeType, data)
|
||||
|
||||
contentPart := `{"type":"image_url","image_url":{"url":""}}`
|
||||
contentPart, _ = sjson.Set(contentPart, "image_url.url", imageURL)
|
||||
contentWrapper, _ = sjson.SetRaw(contentWrapper, "arr.-1", contentPart)
|
||||
contentPart := []byte(`{"type":"image_url","image_url":{"url":""}}`)
|
||||
contentPart, _ = sjson.SetBytes(contentPart, "image_url.url", imageURL)
|
||||
contentWrapper, _ = sjson.SetRawBytes(contentWrapper, "arr.-1", contentPart)
|
||||
contentPartsCount++
|
||||
}
|
||||
|
||||
@@ -212,32 +212,32 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
toolCallID := genToolCallID()
|
||||
toolCallIDs = append(toolCallIDs, toolCallID)
|
||||
|
||||
toolCall := `{"id":"","type":"function","function":{"name":"","arguments":""}}`
|
||||
toolCall, _ = sjson.Set(toolCall, "id", toolCallID)
|
||||
toolCall, _ = sjson.Set(toolCall, "function.name", functionCall.Get("name").String())
|
||||
toolCall := []byte(`{"id":"","type":"function","function":{"name":"","arguments":""}}`)
|
||||
toolCall, _ = sjson.SetBytes(toolCall, "id", toolCallID)
|
||||
toolCall, _ = sjson.SetBytes(toolCall, "function.name", functionCall.Get("name").String())
|
||||
|
||||
// Convert args to arguments JSON string
|
||||
if args := functionCall.Get("args"); args.Exists() {
|
||||
toolCall, _ = sjson.Set(toolCall, "function.arguments", args.Raw)
|
||||
toolCall, _ = sjson.SetBytes(toolCall, "function.arguments", args.Raw)
|
||||
} else {
|
||||
toolCall, _ = sjson.Set(toolCall, "function.arguments", "{}")
|
||||
toolCall, _ = sjson.SetBytes(toolCall, "function.arguments", "{}")
|
||||
}
|
||||
|
||||
toolCallsWrapper, _ = sjson.SetRaw(toolCallsWrapper, "arr.-1", toolCall)
|
||||
toolCallsWrapper, _ = sjson.SetRawBytes(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 := `{"role":"tool","tool_call_id":"","content":""}`
|
||||
toolMsg := []byte(`{"role":"tool","tool_call_id":"","content":""}`)
|
||||
|
||||
// Convert response.content to JSON string
|
||||
if response := functionResponse.Get("response"); response.Exists() {
|
||||
if contentField := response.Get("content"); contentField.Exists() {
|
||||
toolMsg, _ = sjson.Set(toolMsg, "content", contentField.Raw)
|
||||
toolMsg, _ = sjson.SetBytes(toolMsg, "content", contentField.Raw)
|
||||
} else {
|
||||
toolMsg, _ = sjson.Set(toolMsg, "content", response.Raw)
|
||||
toolMsg, _ = sjson.SetBytes(toolMsg, "content", response.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,13 +246,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, _ = sjson.Set(toolMsg, "tool_call_id", toolCallIDs[len(toolCallIDs)-1])
|
||||
toolMsg, _ = sjson.SetBytes(toolMsg, "tool_call_id", toolCallIDs[len(toolCallIDs)-1])
|
||||
} else {
|
||||
// Generate a tool call ID if none available
|
||||
toolMsg, _ = sjson.Set(toolMsg, "tool_call_id", genToolCallID())
|
||||
toolMsg, _ = sjson.SetBytes(toolMsg, "tool_call_id", genToolCallID())
|
||||
}
|
||||
|
||||
out, _ = sjson.SetRaw(out, "messages.-1", toolMsg)
|
||||
out, _ = sjson.SetRawBytes(out, "messages.-1", toolMsg)
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -262,18 +262,18 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
// Set content
|
||||
if contentPartsCount > 0 {
|
||||
if onlyTextContent {
|
||||
msg, _ = sjson.Set(msg, "content", textBuilder.String())
|
||||
msg, _ = sjson.SetBytes(msg, "content", textBuilder.String())
|
||||
} else {
|
||||
msg, _ = sjson.SetRaw(msg, "content", gjson.Get(contentWrapper, "arr").Raw)
|
||||
msg, _ = sjson.SetRawBytes(msg, "content", []byte(gjson.GetBytes(contentWrapper, "arr").Raw))
|
||||
}
|
||||
}
|
||||
|
||||
// Set tool calls if any
|
||||
if toolCallsCount > 0 {
|
||||
msg, _ = sjson.SetRaw(msg, "tool_calls", gjson.Get(toolCallsWrapper, "arr").Raw)
|
||||
msg, _ = sjson.SetRawBytes(msg, "tool_calls", []byte(gjson.GetBytes(toolCallsWrapper, "arr").Raw))
|
||||
}
|
||||
|
||||
out, _ = sjson.SetRaw(out, "messages.-1", msg)
|
||||
out, _ = sjson.SetRawBytes(out, "messages.-1", msg)
|
||||
return true
|
||||
})
|
||||
}
|
||||
@@ -283,18 +283,18 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if functionDeclarations := tool.Get("functionDeclarations"); functionDeclarations.Exists() && functionDeclarations.IsArray() {
|
||||
functionDeclarations.ForEach(func(_, funcDecl gjson.Result) bool {
|
||||
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())
|
||||
openAITool := []byte(`{"type":"function","function":{"name":"","description":""}}`)
|
||||
openAITool, _ = sjson.SetBytes(openAITool, "function.name", funcDecl.Get("name").String())
|
||||
openAITool, _ = sjson.SetBytes(openAITool, "function.description", funcDecl.Get("description").String())
|
||||
|
||||
// Convert parameters schema
|
||||
if parameters := funcDecl.Get("parameters"); parameters.Exists() {
|
||||
openAITool, _ = sjson.SetRaw(openAITool, "function.parameters", parameters.Raw)
|
||||
openAITool, _ = sjson.SetRawBytes(openAITool, "function.parameters", []byte(parameters.Raw))
|
||||
} else if parameters := funcDecl.Get("parametersJsonSchema"); parameters.Exists() {
|
||||
openAITool, _ = sjson.SetRaw(openAITool, "function.parameters", parameters.Raw)
|
||||
openAITool, _ = sjson.SetRawBytes(openAITool, "function.parameters", []byte(parameters.Raw))
|
||||
}
|
||||
|
||||
out, _ = sjson.SetRaw(out, "tools.-1", openAITool)
|
||||
out, _ = sjson.SetRawBytes(out, "tools.-1", openAITool)
|
||||
return true
|
||||
})
|
||||
}
|
||||
@@ -308,14 +308,14 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
mode := functionCallingConfig.Get("mode").String()
|
||||
switch mode {
|
||||
case "NONE":
|
||||
out, _ = sjson.Set(out, "tool_choice", "none")
|
||||
out, _ = sjson.SetBytes(out, "tool_choice", "none")
|
||||
case "AUTO":
|
||||
out, _ = sjson.Set(out, "tool_choice", "auto")
|
||||
out, _ = sjson.SetBytes(out, "tool_choice", "auto")
|
||||
case "ANY":
|
||||
out, _ = sjson.Set(out, "tool_choice", "required")
|
||||
out, _ = sjson.SetBytes(out, "tool_choice", "required")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(out)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
@@ -44,8 +45,8 @@ type ToolCallAccumulator struct {
|
||||
// - param: A pointer to a parameter object for the conversion.
|
||||
//
|
||||
// Returns:
|
||||
// - []string: A slice of strings, each containing a Gemini-compatible JSON response.
|
||||
func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []string {
|
||||
// - [][]byte: A slice of Gemini-compatible JSON responses.
|
||||
func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte {
|
||||
if *param == nil {
|
||||
*param = &ConvertOpenAIResponseToGeminiParams{
|
||||
ToolCallsAccumulator: nil,
|
||||
@@ -55,8 +56,8 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
|
||||
}
|
||||
|
||||
// Handle [DONE] marker
|
||||
if strings.TrimSpace(string(rawJSON)) == "[DONE]" {
|
||||
return []string{}
|
||||
if bytes.Equal(bytes.TrimSpace(rawJSON), []byte("[DONE]")) {
|
||||
return [][]byte{}
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(rawJSON, []byte("data:")) {
|
||||
@@ -76,51 +77,51 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
|
||||
if len(choices.Array()) == 0 {
|
||||
// This is a usage-only chunk, handle usage and return
|
||||
if usage := root.Get("usage"); usage.Exists() {
|
||||
template := `{"candidates":[],"usageMetadata":{}}`
|
||||
template := []byte(`{"candidates":[],"usageMetadata":{}}`)
|
||||
|
||||
// Set model if available
|
||||
if model := root.Get("model"); model.Exists() {
|
||||
template, _ = sjson.Set(template, "model", model.String())
|
||||
template, _ = sjson.SetBytes(template, "model", model.String())
|
||||
}
|
||||
|
||||
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())
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.promptTokenCount", usage.Get("prompt_tokens").Int())
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.candidatesTokenCount", usage.Get("completion_tokens").Int())
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.totalTokenCount", usage.Get("total_tokens").Int())
|
||||
if reasoningTokens := reasoningTokensFromUsage(usage); reasoningTokens > 0 {
|
||||
template, _ = sjson.Set(template, "usageMetadata.thoughtsTokenCount", reasoningTokens)
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.thoughtsTokenCount", reasoningTokens)
|
||||
}
|
||||
return []string{template}
|
||||
return [][]byte{template}
|
||||
}
|
||||
return []string{}
|
||||
return [][]byte{}
|
||||
}
|
||||
|
||||
var results []string
|
||||
var results [][]byte
|
||||
|
||||
choices.ForEach(func(choiceIndex, choice gjson.Result) bool {
|
||||
// Base Gemini response template without finishReason; set when known
|
||||
template := `{"candidates":[{"content":{"parts":[],"role":"model"},"index":0}]}`
|
||||
template := []byte(`{"candidates":[{"content":{"parts":[],"role":"model"},"index":0}]}`)
|
||||
|
||||
// Set model if available
|
||||
if model := root.Get("model"); model.Exists() {
|
||||
template, _ = sjson.Set(template, "model", model.String())
|
||||
template, _ = sjson.SetBytes(template, "model", model.String())
|
||||
}
|
||||
|
||||
_ = int(choice.Get("index").Int()) // choiceIdx not used in streaming
|
||||
delta := choice.Get("delta")
|
||||
baseTemplate := template
|
||||
baseTemplate := append([]byte(nil), template...)
|
||||
|
||||
// Handle role (only in first chunk)
|
||||
if role := delta.Get("role"); role.Exists() && (*param).(*ConvertOpenAIResponseToGeminiParams).IsFirstChunk {
|
||||
// OpenAI assistant -> Gemini model
|
||||
if role.String() == "assistant" {
|
||||
template, _ = sjson.Set(template, "candidates.0.content.role", "model")
|
||||
template, _ = sjson.SetBytes(template, "candidates.0.content.role", "model")
|
||||
}
|
||||
(*param).(*ConvertOpenAIResponseToGeminiParams).IsFirstChunk = false
|
||||
results = append(results, template)
|
||||
return true
|
||||
}
|
||||
|
||||
var chunkOutputs []string
|
||||
var chunkOutputs [][]byte
|
||||
|
||||
// Handle reasoning/thinking delta
|
||||
if reasoning := delta.Get("reasoning_content"); reasoning.Exists() {
|
||||
@@ -128,9 +129,9 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
|
||||
if reasoningText == "" {
|
||||
continue
|
||||
}
|
||||
reasoningTemplate := baseTemplate
|
||||
reasoningTemplate, _ = sjson.Set(reasoningTemplate, "candidates.0.content.parts.0.thought", true)
|
||||
reasoningTemplate, _ = sjson.Set(reasoningTemplate, "candidates.0.content.parts.0.text", reasoningText)
|
||||
reasoningTemplate := append([]byte(nil), baseTemplate...)
|
||||
reasoningTemplate, _ = sjson.SetBytes(reasoningTemplate, "candidates.0.content.parts.0.thought", true)
|
||||
reasoningTemplate, _ = sjson.SetBytes(reasoningTemplate, "candidates.0.content.parts.0.text", reasoningText)
|
||||
chunkOutputs = append(chunkOutputs, reasoningTemplate)
|
||||
}
|
||||
}
|
||||
@@ -141,8 +142,8 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
|
||||
(*param).(*ConvertOpenAIResponseToGeminiParams).ContentAccumulator.WriteString(contentText)
|
||||
|
||||
// Create text part for this delta
|
||||
contentTemplate := baseTemplate
|
||||
contentTemplate, _ = sjson.Set(contentTemplate, "candidates.0.content.parts.0.text", contentText)
|
||||
contentTemplate := append([]byte(nil), baseTemplate...)
|
||||
contentTemplate, _ = sjson.SetBytes(contentTemplate, "candidates.0.content.parts.0.text", contentText)
|
||||
chunkOutputs = append(chunkOutputs, contentTemplate)
|
||||
}
|
||||
|
||||
@@ -207,7 +208,7 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
|
||||
// Handle finish reason
|
||||
if finishReason := choice.Get("finish_reason"); finishReason.Exists() {
|
||||
geminiFinishReason := mapOpenAIFinishReasonToGemini(finishReason.String())
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", geminiFinishReason)
|
||||
template, _ = sjson.SetBytes(template, "candidates.0.finishReason", geminiFinishReason)
|
||||
|
||||
// If we have accumulated tool calls, output them now
|
||||
if len((*param).(*ConvertOpenAIResponseToGeminiParams).ToolCallsAccumulator) > 0 {
|
||||
@@ -215,8 +216,8 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
|
||||
for _, accumulator := range (*param).(*ConvertOpenAIResponseToGeminiParams).ToolCallsAccumulator {
|
||||
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()))
|
||||
template, _ = sjson.SetBytes(template, namePath, accumulator.Name)
|
||||
template, _ = sjson.SetRawBytes(template, argsPath, []byte(parseArgsToObjectRaw(accumulator.Arguments.String())))
|
||||
partIndex++
|
||||
}
|
||||
|
||||
@@ -230,11 +231,11 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
|
||||
|
||||
// Handle usage information
|
||||
if usage := root.Get("usage"); usage.Exists() {
|
||||
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())
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.promptTokenCount", usage.Get("prompt_tokens").Int())
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.candidatesTokenCount", usage.Get("completion_tokens").Int())
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.totalTokenCount", usage.Get("total_tokens").Int())
|
||||
if reasoningTokens := reasoningTokensFromUsage(usage); reasoningTokens > 0 {
|
||||
template, _ = sjson.Set(template, "usageMetadata.thoughtsTokenCount", reasoningTokens)
|
||||
template, _ = sjson.SetBytes(template, "usageMetadata.thoughtsTokenCount", reasoningTokens)
|
||||
}
|
||||
results = append(results, template)
|
||||
return true
|
||||
@@ -244,7 +245,7 @@ func ConvertOpenAIResponseToGemini(_ context.Context, _ string, originalRequestR
|
||||
})
|
||||
return results
|
||||
}
|
||||
return []string{}
|
||||
return [][]byte{}
|
||||
}
|
||||
|
||||
// mapOpenAIFinishReasonToGemini maps OpenAI finish reasons to Gemini finish reasons
|
||||
@@ -310,7 +311,7 @@ func tolerantParseJSONObjectRaw(s string) string {
|
||||
runes := []rune(content)
|
||||
n := len(runes)
|
||||
i := 0
|
||||
result := "{}"
|
||||
result := []byte(`{}`)
|
||||
|
||||
for i < n {
|
||||
// Skip whitespace and commas
|
||||
@@ -362,10 +363,10 @@ func tolerantParseJSONObjectRaw(s string) string {
|
||||
valToken, ni := parseJSONStringRunes(runes, i)
|
||||
if ni == -1 {
|
||||
// Malformed; treat as empty string
|
||||
result, _ = sjson.Set(result, sjsonKey, "")
|
||||
result, _ = sjson.SetBytes(result, sjsonKey, "")
|
||||
i = n
|
||||
} else {
|
||||
result, _ = sjson.Set(result, sjsonKey, jsonStringTokenToRawString(valToken))
|
||||
result, _ = sjson.SetBytes(result, sjsonKey, jsonStringTokenToRawString(valToken))
|
||||
i = ni
|
||||
}
|
||||
case '{', '[':
|
||||
@@ -375,9 +376,9 @@ func tolerantParseJSONObjectRaw(s string) string {
|
||||
i = n
|
||||
} else {
|
||||
if gjson.Valid(seg) {
|
||||
result, _ = sjson.SetRaw(result, sjsonKey, seg)
|
||||
result, _ = sjson.SetRawBytes(result, sjsonKey, []byte(seg))
|
||||
} else {
|
||||
result, _ = sjson.Set(result, sjsonKey, seg)
|
||||
result, _ = sjson.SetBytes(result, sjsonKey, seg)
|
||||
}
|
||||
i = ni
|
||||
}
|
||||
@@ -390,15 +391,15 @@ func tolerantParseJSONObjectRaw(s string) string {
|
||||
token := strings.TrimSpace(string(runes[i:j]))
|
||||
// Interpret common JSON atoms and numbers; otherwise treat as string
|
||||
if token == "true" {
|
||||
result, _ = sjson.Set(result, sjsonKey, true)
|
||||
result, _ = sjson.SetBytes(result, sjsonKey, true)
|
||||
} else if token == "false" {
|
||||
result, _ = sjson.Set(result, sjsonKey, false)
|
||||
result, _ = sjson.SetBytes(result, sjsonKey, false)
|
||||
} else if token == "null" {
|
||||
result, _ = sjson.Set(result, sjsonKey, nil)
|
||||
result, _ = sjson.SetBytes(result, sjsonKey, nil)
|
||||
} else if numVal, ok := tryParseNumber(token); ok {
|
||||
result, _ = sjson.Set(result, sjsonKey, numVal)
|
||||
result, _ = sjson.SetBytes(result, sjsonKey, numVal)
|
||||
} else {
|
||||
result, _ = sjson.Set(result, sjsonKey, token)
|
||||
result, _ = sjson.SetBytes(result, sjsonKey, token)
|
||||
}
|
||||
i = j
|
||||
}
|
||||
@@ -412,7 +413,7 @@ func tolerantParseJSONObjectRaw(s string) string {
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// parseJSONStringRunes returns the JSON string token (including quotes) and the index just after it.
|
||||
@@ -531,16 +532,16 @@ func tryParseNumber(s string) (interface{}, bool) {
|
||||
// - param: A pointer to a parameter object for the conversion.
|
||||
//
|
||||
// Returns:
|
||||
// - string: A Gemini-compatible JSON response.
|
||||
func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) string {
|
||||
// - []byte: A Gemini-compatible JSON response.
|
||||
func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) []byte {
|
||||
root := gjson.ParseBytes(rawJSON)
|
||||
|
||||
// Base Gemini response template without finishReason; set when known
|
||||
out := `{"candidates":[{"content":{"parts":[],"role":"model"},"index":0}]}`
|
||||
out := []byte(`{"candidates":[{"content":{"parts":[],"role":"model"},"index":0}]}`)
|
||||
|
||||
// Set model if available
|
||||
if model := root.Get("model"); model.Exists() {
|
||||
out, _ = sjson.Set(out, "model", model.String())
|
||||
out, _ = sjson.SetBytes(out, "model", model.String())
|
||||
}
|
||||
|
||||
// Process choices
|
||||
@@ -552,7 +553,7 @@ func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, origina
|
||||
// Set role
|
||||
if role := message.Get("role"); role.Exists() {
|
||||
if role.String() == "assistant" {
|
||||
out, _ = sjson.Set(out, "candidates.0.content.role", "model")
|
||||
out, _ = sjson.SetBytes(out, "candidates.0.content.role", "model")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,15 +565,15 @@ func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, origina
|
||||
if reasoningText == "" {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
out, _ = sjson.SetBytes(out, fmt.Sprintf("candidates.0.content.parts.%d.thought", partIndex), true)
|
||||
out, _ = sjson.SetBytes(out, fmt.Sprintf("candidates.0.content.parts.%d.text", partIndex), reasoningText)
|
||||
partIndex++
|
||||
}
|
||||
}
|
||||
|
||||
// Handle content first
|
||||
if content := message.Get("content"); content.Exists() && content.String() != "" {
|
||||
out, _ = sjson.Set(out, fmt.Sprintf("candidates.0.content.parts.%d.text", partIndex), content.String())
|
||||
out, _ = sjson.SetBytes(out, fmt.Sprintf("candidates.0.content.parts.%d.text", partIndex), content.String())
|
||||
partIndex++
|
||||
}
|
||||
|
||||
@@ -586,8 +587,8 @@ func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, origina
|
||||
|
||||
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))
|
||||
out, _ = sjson.SetBytes(out, namePath, functionName)
|
||||
out, _ = sjson.SetRawBytes(out, argsPath, []byte(parseArgsToObjectRaw(functionArgs)))
|
||||
partIndex++
|
||||
}
|
||||
return true
|
||||
@@ -597,11 +598,11 @@ func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, origina
|
||||
// Handle finish reason
|
||||
if finishReason := choice.Get("finish_reason"); finishReason.Exists() {
|
||||
geminiFinishReason := mapOpenAIFinishReasonToGemini(finishReason.String())
|
||||
out, _ = sjson.Set(out, "candidates.0.finishReason", geminiFinishReason)
|
||||
out, _ = sjson.SetBytes(out, "candidates.0.finishReason", geminiFinishReason)
|
||||
}
|
||||
|
||||
// Set index
|
||||
out, _ = sjson.Set(out, "candidates.0.index", choiceIdx)
|
||||
out, _ = sjson.SetBytes(out, "candidates.0.index", choiceIdx)
|
||||
|
||||
return true
|
||||
})
|
||||
@@ -609,19 +610,19 @@ func ConvertOpenAIResponseToGeminiNonStream(_ context.Context, _ string, origina
|
||||
|
||||
// Handle usage information
|
||||
if usage := root.Get("usage"); usage.Exists() {
|
||||
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())
|
||||
out, _ = sjson.SetBytes(out, "usageMetadata.promptTokenCount", usage.Get("prompt_tokens").Int())
|
||||
out, _ = sjson.SetBytes(out, "usageMetadata.candidatesTokenCount", usage.Get("completion_tokens").Int())
|
||||
out, _ = sjson.SetBytes(out, "usageMetadata.totalTokenCount", usage.Get("total_tokens").Int())
|
||||
if reasoningTokens := reasoningTokensFromUsage(usage); reasoningTokens > 0 {
|
||||
out, _ = sjson.Set(out, "usageMetadata.thoughtsTokenCount", reasoningTokens)
|
||||
out, _ = sjson.SetBytes(out, "usageMetadata.thoughtsTokenCount", reasoningTokens)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func GeminiTokenCount(ctx context.Context, count int64) string {
|
||||
return fmt.Sprintf(`{"totalTokens":%d,"promptTokensDetails":[{"modality":"TEXT","tokenCount":%d}]}`, count, count)
|
||||
func GeminiTokenCount(ctx context.Context, count int64) []byte {
|
||||
return translatorcommon.GeminiTokenCountJSON(count)
|
||||
}
|
||||
|
||||
func reasoningTokensFromUsage(usage gjson.Result) int64 {
|
||||
|
||||
Reference in New Issue
Block a user