Skip to content

Commit 217eb7d

Browse files
authored
fix: preserve thought_signature across parallel tool calls for Gemini 3+ (#1000)
1 parent ebfb82f commit 217eb7d

3 files changed

Lines changed: 18 additions & 4 deletions

File tree

.changeset/salty-regions-relate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@livekit/agents': patch
3+
---
4+
5+
preserve thought_signature across parallel tool calls for Gemini 3+ for inference gateway

agents/src/inference/llm.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,10 @@ export class LLMStream extends llm.LLMStream {
433433
if (this.toolCallId && tool.id && tool.index !== this.toolIndex) {
434434
callChunk = this.createRunningToolCallChunk(id, delta);
435435
this.toolCallId = this.fncName = this.fncRawArguments = undefined;
436-
this.toolExtra = undefined;
436+
// Note: We intentionally do NOT reset toolExtra here.
437+
// For Gemini 3+, the thought_signature is only provided on the first tool call
438+
// in a parallel batch, but must be applied to ALL tool calls in the batch.
439+
// We preserve toolExtra so subsequent tool calls inherit the thought_signature.
437440
}
438441

439442
// Start or continue building the current tool call
@@ -443,9 +446,14 @@ export class LLMStream extends llm.LLMStream {
443446
this.fncName = tool.function.name;
444447
this.fncRawArguments = tool.function.arguments || '';
445448
// Extract extra from tool call (e.g., Google thought signatures)
446-
this.toolExtra =
449+
// Only update toolExtra if this tool call has extra_content.
450+
// Otherwise, inherit from previous tool call (for parallel Gemini tool calls).
451+
const newToolExtra =
447452
// eslint-disable-next-line @typescript-eslint/no-explicit-any
448453
((tool as any).extra_content as Record<string, unknown> | undefined) ?? undefined;
454+
if (newToolExtra) {
455+
this.toolExtra = newToolExtra;
456+
}
449457
} else if (tool.function.arguments) {
450458
this.fncRawArguments = (this.fncRawArguments || '') + tool.function.arguments;
451459
}
@@ -464,6 +472,7 @@ export class LLMStream extends llm.LLMStream {
464472
) {
465473
const callChunk = this.createRunningToolCallChunk(id, delta);
466474
this.toolCallId = this.fncName = this.fncRawArguments = undefined;
475+
// Reset toolExtra at the end of the response (not between parallel tool calls)
467476
this.toolExtra = undefined;
468477
return callChunk;
469478
}

agents/src/voice/agent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,16 +325,16 @@ export class Agent<UserData = any> {
325325
);
326326
}
327327

328-
// TODO(brian): make parallelToolCalls configurable
329328
const { toolChoice } = modelSettings;
330329
const connOptions = activity.agentSession.connOptions.llmConnOptions;
331330

331+
// parallelToolCalls is not passed here - it will use the value from LLM's modelOptions
332+
// This allows users to configure it via: new inference.LLM({ modelOptions: { parallel_tool_calls: false } })
332333
const stream = activity.llm.chat({
333334
chatCtx,
334335
toolCtx,
335336
toolChoice,
336337
connOptions,
337-
parallelToolCalls: true,
338338
});
339339

340340
let cleaned = false;

0 commit comments

Comments
 (0)