From fa8d94971f8e3cd439603c843aa25cb845b49bb6 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Thu, 10 Jul 2025 22:26:04 +0800 Subject: [PATCH] Enhance response and request handling in translators - Refactored response handling to process multiple content parts effectively. - Improved `tool_calls` structure with unique ID generation and enhanced mapping logic. - Simplified `SystemInstruction` and tool message parsing in requests for better accuracy. - Enhanced handling of function calls and tool responses with improved data integration. --- internal/api/translator/request.go | 77 +++++++++++++++++++++-------- internal/api/translator/response.go | 59 ++++++++++++---------- 2 files changed, 91 insertions(+), 45 deletions(-) diff --git a/internal/api/translator/request.go b/internal/api/translator/request.go index 634e74e8..64517f5b 100644 --- a/internal/api/translator/request.go +++ b/internal/api/translator/request.go @@ -24,6 +24,39 @@ func PrepareRequest(rawJson []byte) (string, *client.Content, []client.Content, contents := make([]client.Content, 0) var systemInstruction *client.Content messagesResult := gjson.GetBytes(rawJson, "messages") + + toolItems := make(map[string]*client.FunctionResponse) + if messagesResult.IsArray() { + messagesResults := messagesResult.Array() + for i := 0; i < len(messagesResults); i++ { + messageResult := messagesResults[i] + roleResult := messageResult.Get("role") + if roleResult.Type != gjson.String { + continue + } + contentResult := messageResult.Get("content") + if roleResult.String() == "tool" { + toolCallID := messageResult.Get("tool_call_id").String() + if toolCallID != "" { + var responseData string + if contentResult.Type == gjson.String { + responseData = contentResult.String() + } else if contentResult.IsObject() && contentResult.Get("type").String() == "text" { + responseData = contentResult.Get("text").String() + } + + // drop the timestamp from the tool call ID + toolCallIDs := strings.Split(toolCallID, "-") + strings.Join(toolCallIDs, "-") + newToolCallID := strings.Join(toolCallIDs[:len(toolCallIDs)-1], "-") + + functionResponse := client.FunctionResponse{Name: newToolCallID, Response: map[string]interface{}{"result": responseData}} + toolItems[toolCallID] = &functionResponse + } + } + } + } + if messagesResult.IsArray() { messagesResults := messagesResult.Array() for i := 0; i < len(messagesResults); i++ { @@ -97,40 +130,44 @@ func PrepareRequest(rawJson []byte) (string, *client.Content, []client.Content, contents = append(contents, client.Content{Role: "model", Parts: []client.Part{{Text: contentResult.String()}}}) } else if !contentResult.Exists() || contentResult.Type == gjson.Null { // Handle tool calls made by the assistant. + functionIDs := make([]string, 0) toolCallsResult := messageResult.Get("tool_calls") if toolCallsResult.IsArray() { + parts := make([]client.Part, 0) tcsResult := toolCallsResult.Array() for j := 0; j < len(tcsResult); j++ { tcResult := tcsResult[j] + + functionID := tcResult.Get("id").String() + functionIDs = append(functionIDs, functionID) + functionName := tcResult.Get("function.name").String() functionArgs := tcResult.Get("function.arguments").String() var args map[string]any if err := json.Unmarshal([]byte(functionArgs), &args); err == nil { - contents = append(contents, client.Content{ - Role: "model", Parts: []client.Part{{ - FunctionCall: &client.FunctionCall{ - Name: functionName, - Args: args, - }, - }}, + parts = append(parts, client.Part{ + FunctionCall: &client.FunctionCall{ + Name: functionName, + Args: args, + }, }) } } + if len(parts) > 0 { + contents = append(contents, client.Content{ + Role: "model", Parts: parts, + }) + + toolParts := make([]client.Part, 0) + for _, functionID := range functionIDs { + if functionResponse, ok := toolItems[functionID]; ok { + toolParts = append(toolParts, client.Part{FunctionResponse: functionResponse}) + } + } + contents = append(contents, client.Content{Role: "tool", Parts: toolParts}) + } } } - // Tool messages contain the output of a tool call. - case "tool": - toolCallID := messageResult.Get("tool_call_id").String() - if toolCallID != "" { - var responseData string - if contentResult.Type == gjson.String { - responseData = contentResult.String() - } else if contentResult.IsObject() && contentResult.Get("type").String() == "text" { - responseData = contentResult.Get("text").String() - } - functionResponse := client.FunctionResponse{Name: toolCallID, Response: map[string]interface{}{"result": responseData}} - contents = append(contents, client.Content{Role: "tool", Parts: []client.Part{{FunctionResponse: &functionResponse}}}) - } } } } diff --git a/internal/api/translator/response.go b/internal/api/translator/response.go index 2fce1679..82c9a551 100644 --- a/internal/api/translator/response.go +++ b/internal/api/translator/response.go @@ -1,6 +1,7 @@ package translator import ( + "fmt" "time" "github.com/tidwall/gjson" @@ -62,32 +63,40 @@ func ConvertCliToOpenAI(rawJson []byte, unixTimestamp int64, isGlAPIKey bool) st } // Process the main content part of the response. - partResult := gjson.GetBytes(rawJson, "response.candidates.0.content.parts.0") - partTextResult := partResult.Get("text") - functionCallResult := partResult.Get("functionCall") + partsResult := gjson.GetBytes(rawJson, "response.candidates.0.content.parts") + if partsResult.IsArray() { + partResults := partsResult.Array() + for i := 0; i < len(partResults); i++ { + partResult := partResults[i] + partTextResult := partResult.Get("text") + functionCallResult := partResult.Get("functionCall") - if partTextResult.Exists() { - // Handle text content, distinguishing between regular content and reasoning/thoughts. - if partResult.Get("thought").Bool() { - template, _ = sjson.Set(template, "choices.0.delta.reasoning_content", partTextResult.String()) - } else { - template, _ = sjson.Set(template, "choices.0.delta.content", partTextResult.String()) + if partTextResult.Exists() { + // Handle text content, distinguishing between regular content and reasoning/thoughts. + if partResult.Get("thought").Bool() { + template, _ = sjson.Set(template, "choices.0.delta.reasoning_content", partTextResult.String()) + } else { + template, _ = sjson.Set(template, "choices.0.delta.content", partTextResult.String()) + } + template, _ = sjson.Set(template, "choices.0.delta.role", "assistant") + } else if functionCallResult.Exists() { + // Handle function call content. + toolCallsResult := gjson.Get(template, "choices.0.delta.tool_calls") + if !toolCallsResult.Exists() || !toolCallsResult.IsArray() { + template, _ = sjson.SetRaw(template, "choices.0.delta.tool_calls", `[]`) + } + + functionCallTemplate := `{"id": "","type": "function","function": {"name": "","arguments": ""}}` + fcName := functionCallResult.Get("name").String() + functionCallTemplate, _ = sjson.Set(functionCallTemplate, "id", fmt.Sprintf("%s-%d", fcName, time.Now().UnixNano())) + functionCallTemplate, _ = sjson.Set(functionCallTemplate, "function.name", fcName) + if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() { + functionCallTemplate, _ = sjson.Set(functionCallTemplate, "function.arguments", fcArgsResult.Raw) + } + template, _ = sjson.Set(template, "choices.0.delta.role", "assistant") + template, _ = sjson.SetRaw(template, "choices.0.message.tool_calls.-1", functionCallTemplate) + } } - template, _ = sjson.Set(template, "choices.0.delta.role", "assistant") - } else if functionCallResult.Exists() { - // Handle function call content. - functionCallTemplate := `[{"id": "","type": "function","function": {"name": "","arguments": ""}}]` - fcName := functionCallResult.Get("name").String() - functionCallTemplate, _ = sjson.Set(functionCallTemplate, "0.id", fcName) - functionCallTemplate, _ = sjson.Set(functionCallTemplate, "0.function.name", fcName) - if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() { - functionCallTemplate, _ = sjson.Set(functionCallTemplate, "0.function.arguments", fcArgsResult.Raw) - } - template, _ = sjson.Set(template, "choices.0.delta.role", "assistant") - template, _ = sjson.SetRaw(template, "choices.0.delta.tool_calls", functionCallTemplate) - } else { - // If no usable content is found, return an empty string. - return "" } return template @@ -163,7 +172,7 @@ func ConvertCliToOpenAINonStream(rawJson []byte, unixTimestamp int64, isGlAPIKey } functionCallItemTemplate := `{"id": "","type": "function","function": {"name": "","arguments": ""}}` fcName := functionCallResult.Get("name").String() - functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "id", fcName) + functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "id", fmt.Sprintf("%s-%d", fcName, time.Now().UnixNano())) functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "function.name", fcName) if fcArgsResult := functionCallResult.Get("args"); fcArgsResult.Exists() { functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "function.arguments", fcArgsResult.Raw)