fix(translator): preserve functionCall id in Gemini to OpenAI request translation (#11365)

Merged via consolidated batch validation. Fixes geminiToOpenAIRequest discarding functionCall.id in favor of a random generated id, causing multi-turn tool-call id mismatches against OpenAI-compatible upstreams. Own test passes.
This commit is contained in:
MSiva
2026-08-24 20:43:02 +05:30
committed by GitHub
parent 077bc1a8a2
commit 37e71915db
2 changed files with 73 additions and 8 deletions

View File

@@ -137,7 +137,7 @@ function convertGeminiContent(content) {
if (part.functionCall) {
toolCalls.push({
id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: part.functionCall.id || `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
type: "function",
function: {
name: part.functionCall.name,

View File

@@ -100,10 +100,7 @@ test("Gemini -> OpenAI maps a thought:true part to reasoning_content instead of
contents: [
{
role: "model",
parts: [
{ thought: true, text: "internal reasoning" },
{ text: "final answer" },
],
parts: [{ thought: true, text: "internal reasoning" }, { text: "final answer" }],
},
],
},
@@ -116,9 +113,7 @@ test("Gemini -> OpenAI maps a thought:true part to reasoning_content instead of
assert.equal(assistant.reasoning_content, "internal reasoning");
// The visible content must not contain the thought text.
const visibleText =
typeof assistant.content === "string"
? assistant.content
: JSON.stringify(assistant.content);
typeof assistant.content === "string" ? assistant.content : JSON.stringify(assistant.content);
assert.doesNotMatch(visibleText, /internal reasoning/);
assert.match(visibleText, /final answer/);
});
@@ -172,3 +167,73 @@ test("Gemini -> OpenAI converts function responses into tool messages", () => {
},
]);
});
test("Gemini -> OpenAI preserves functionCall id when present", () => {
const result = geminiToOpenAIRequest(
"gpt-4o",
{
contents: [
{
role: "model",
parts: [
{
functionCall: {
id: "call_custom_id_999",
name: "get_weather",
args: { city: "Tokyo" },
},
},
],
},
],
},
false
);
assert.equal(result.messages.length, 1);
assert.equal(result.messages[0].role, "assistant");
assert.equal(result.messages[0].tool_calls[0].id, "call_custom_id_999");
assert.equal(result.messages[0].tool_calls[0].function.name, "get_weather");
});
test("Gemini -> OpenAI maintains matching IDs across multi-turn tool call and response", () => {
const result = geminiToOpenAIRequest(
"gpt-4o",
{
contents: [
{
role: "model",
parts: [
{
functionCall: {
id: "call_calc_456",
name: "calculator",
args: { expr: "2 + 2" },
},
},
],
},
{
role: "user",
parts: [
{
functionResponse: {
id: "call_calc_456",
name: "calculator",
response: { result: 4 },
},
},
],
},
],
},
false
);
assert.equal(result.messages.length, 2);
const assistantCallId = result.messages[0].tool_calls[0].id;
const toolResponseCallId = result.messages[1].tool_call_id;
assert.equal(assistantCallId, "call_calc_456");
assert.equal(toolResponseCallId, "call_calc_456");
assert.equal(assistantCallId, toolResponseCallId);
});