diff --git a/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go b/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go index e0cd60e5..22ce913e 100644 --- a/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go +++ b/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go @@ -33,7 +33,83 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte // Convert input messages to Gemini contents format if input := root.Get("input"); input.Exists() && input.IsArray() { - input.ForEach(func(_, item gjson.Result) bool { + items := input.Array() + + // Normalize consecutive function calls and outputs so each call is immediately followed by its response + normalized := make([]gjson.Result, 0, len(items)) + for i := 0; i < len(items); { + item := items[i] + itemType := item.Get("type").String() + itemRole := item.Get("role").String() + if itemType == "" && itemRole != "" { + itemType = "message" + } + + if itemType == "function_call" { + var calls []gjson.Result + var outputs []gjson.Result + + for i < len(items) { + next := items[i] + nextType := next.Get("type").String() + nextRole := next.Get("role").String() + if nextType == "" && nextRole != "" { + nextType = "message" + } + if nextType != "function_call" { + break + } + calls = append(calls, next) + i++ + } + + for i < len(items) { + next := items[i] + nextType := next.Get("type").String() + nextRole := next.Get("role").String() + if nextType == "" && nextRole != "" { + nextType = "message" + } + if nextType != "function_call_output" { + break + } + outputs = append(outputs, next) + i++ + } + + if len(calls) > 0 { + outputMap := make(map[string]gjson.Result, len(outputs)) + for _, out := range outputs { + outputMap[out.Get("call_id").String()] = out + } + for _, call := range calls { + normalized = append(normalized, call) + callID := call.Get("call_id").String() + if resp, ok := outputMap[callID]; ok { + normalized = append(normalized, resp) + delete(outputMap, callID) + } + } + for _, out := range outputs { + if _, ok := outputMap[out.Get("call_id").String()]; ok { + normalized = append(normalized, out) + } + } + continue + } + } + + if itemType == "function_call_output" { + normalized = append(normalized, item) + i++ + continue + } + + normalized = append(normalized, item) + i++ + } + + for _, item := range normalized { itemType := item.Get("type").String() itemRole := item.Get("role").String() if itemType == "" && itemRole != "" { @@ -59,7 +135,7 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte out, _ = sjson.SetRaw(out, "system_instruction", systemInstr) } } - return true + continue } // Handle regular messages @@ -222,8 +298,7 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte functionContent, _ = sjson.SetRaw(functionContent, "parts.-1", functionResponse) out, _ = sjson.SetRaw(out, "contents.-1", functionContent) } - return true - }) + } } else if input.Exists() && input.Type == gjson.String { // Simple string input conversion to user message userContent := `{"role":"user","parts":[{"text":""}]}`