Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clever-feet-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@livekit/agents-plugin-google": patch
---

fix(google): align realtime behavior for Gemini 3.1 and harden session/tool handling
1 change: 1 addition & 0 deletions plugins/google/src/beta/realtime/api_proto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type LiveAPIModels =
| 'gemini-live-2.5-flash-preview-native-audio-09-2025' // Public preview https://docs.cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-flash-live-api#live-2.5-flash-preview
| 'gemini-live-2.5-flash-preview-native-audio' // still works, possibly an alias, but not mentioned in any docs or changelog
// Gemini API models
| 'gemini-3.1-flash-live-preview' // https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-live-preview
| 'gemini-2.5-flash-native-audio-preview-12-2025' // https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-live
| 'gemini-2.5-flash-native-audio-preview-09-2025' // https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-live
| 'gemini-2.0-flash-exp'; // still works in Gemini API but not VertexAI
Expand Down
50 changes: 34 additions & 16 deletions plugins/google/src/beta/realtime/realtime_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,12 +660,10 @@ export class RealtimeSession extends llm.RealtimeSession {
for (const f of this.resampleAudio(frame)) {
for (const nf of this.bstream.write(f.data.buffer as ArrayBuffer)) {
const realtimeInput: types.LiveClientRealtimeInput = {
mediaChunks: [
{
mimeType: 'audio/pcm',
data: Buffer.from(nf.data.buffer).toString('base64'),
},
],
audio: {
mimeType: 'audio/pcm',
data: Buffer.from(nf.data.buffer).toString('base64'),
},
};
this.sendClientEvent({
type: 'realtime_input',
Expand All @@ -684,6 +682,13 @@ export class RealtimeSession extends llm.RealtimeSession {
}

async generateReply(instructions?: string): Promise<llm.GenerationCreatedEvent> {
if (this.options.model === 'gemini-3.1-flash-live-preview') {
this.#logger.warn(
'generate_reply is not compatible with gemini-3.1-flash-live-preview and will be ignored.',
);
Comment thread
toubatbrian marked this conversation as resolved.
throw new Error("generate_reply is not compatible with 'gemini-3.1-flash-live-preview'");
}

if (this.pendingGenerationFut && !this.pendingGenerationFut.done) {
this.#logger.warn(
'generateReply called while another generation is pending, cancelling previous.',
Expand Down Expand Up @@ -992,13 +997,16 @@ export class RealtimeSession extends llm.RealtimeSession {
}
break;
case 'realtime_input':
const { mediaChunks, activityStart, activityEnd, text } = msg.value;
const { mediaChunks, audio, activityStart, activityEnd, text } = msg.value;
if (this.pendingToolCallIds.size > 0) break;
if (mediaChunks) {
for (const mediaChunk of mediaChunks) {
await session.sendRealtimeInput({ media: mediaChunk });
}
}
if (audio) {
await session.sendRealtimeInput({ audio });
}
if (text) {
await session.sendRealtimeInput({ text });
}
Expand Down Expand Up @@ -1152,6 +1160,16 @@ export class RealtimeSession extends llm.RealtimeSession {
),
};
}
if (obj.type === 'realtime_input' && obj.value?.audio) {
const ac = obj.value.audio as { mimeType?: string; data?: string };
obj.value = {
...obj.value,
audio: {
...ac,
data: typeof ac.data === 'string' ? this.truncateString(ac.data, maxLength) : ac.data,
},
};
}
return obj;
}

Expand Down Expand Up @@ -1265,17 +1283,15 @@ export class RealtimeSession extends llm.RealtimeSession {
},
languageCode: opts.language,
},
tools: [
{
functionDeclarations: this.geminiDeclarations,
...this.options.geminiTools,
},
],
tools:
this.geminiDeclarations.length > 0 || this.options.geminiTools
? [{ functionDeclarations: this.geminiDeclarations, ...this.options.geminiTools }]
: undefined,
inputAudioTranscription: opts.inputAudioTranscription,
outputAudioTranscription: opts.outputAudioTranscription,
sessionResumption: {
handle: this.sessionResumptionHandle,
},
sessionResumption: this.sessionResumptionHandle
? { handle: this.sessionResumptionHandle }
: {},
};

// Add generation fields at TOP LEVEL (NO generationConfig!)
Expand Down Expand Up @@ -1527,6 +1543,8 @@ export class RealtimeSession extends llm.RealtimeSession {
}),
);
}
// This closes message/text/audio/function streams so the consumer can continue.
this.markCurrentGenerationDone();
Comment thread
toubatbrian marked this conversation as resolved.
}

private handleToolCallCancellation(cancellation: types.LiveServerToolCallCancellation): void {
Expand Down
Loading