ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Firebase JS SDK LiveServerContent 接口详解:实时语音会话的增量内容更新协议

Firebase JS SDK LiveServerContent 接口详解:实时语音会话的增量内容更新协议
  • 开发工具
  • 后端

【免费下载链接】firebase-js-sdk

Firebase Javascript SDK

项目地址:https://gitcode.com/gh_mirrors/fi/firebase-js-sdk
点击查看免费下载

导读

LiveServerContent是 Firebase JS SDK 中 AI 模块(@firebase/ai)为实时(Live)会话定义的服务器消息类型,用于在 Gemini Live API 双向流式交互中承载模型输出的增量内容更新——包括模型生成的内容片段、轮次结束信号、被打断信号,以及可选的语音输入/输出转写文本。本文将以 docs-devsite/ai.liveservercontent.md 为核心,逐字段剖析该接口的语义,并结合 packages/ai/src/types/responses.ts、packages/ai/src/methods/live-session.ts 等源码与测试用例,讲清它的产生机制、消费方式与典型应用场景。读完本文,你将能准确理解并处理 Live 会话中每一条serverContent消息,正确实现语音打断、轮次判断与转写展示。

LiveServerContent 在实时会话中的定位

Live 会话(由LiveGenerativeModel建立、LiveSession维护)是 WebSocket 之上的全双工实时交互通道:客户端持续发送文本、音频(16kHz/16-bit PCM)或视频帧,服务端则持续回推模型响应。LiveServerContent就是服务端回推消息中最核心、出现频率最高的一类,其类型定义明确写着:

An incremental content update from the model.(模型的一次增量内容更新。)

从源码结构看,LiveSession.receive()返回的异步生成器统一产出五种消息类型(见 packages/ai/src/methods/live-session.ts#L333-L339):

async *receive(): AsyncGenerator< | LiveServerContent | LiveServerToolCall | LiveServerToolCallCancellation | LiveServerGoingAwayNotice | LiveSessionResumptionUpdate >

其中LiveServerContent携带"模型说了什么、轮到谁、有没有被打断、说了哪些话的转写"等会话状态信息,是理解实时对话进程的主线索。

接口签名与属性总览

接口定义于 packages/ai/src/types/responses.ts#L580-L609,标注为@beta(Public Preview)API:

export interface LiveServerContent { type: 'serverContent'; modelTurn?: Content; turnComplete?: boolean; interrupted?: boolean; inputTranscription?: Transcription; outputTranscription?: Transcription; }

各属性语义如下表(与官方 API 文档一致):

属性类型说明
type'serverContent'消息类型判别常量,用于区分toolCall、goingAwayNotice等其它消息
modelTurnContent模型在本轮对话中生成的内容(文本、内联音频等 Part 集合)
turnCompleteboolean是否已到模型回合结束;未结束时为undefined
interruptedboolean模型是否被客户端打断;未被中断时为undefined
inputTranscriptionTranscription输入给模型的音频的语音转写
outputTranscriptionTranscription模型输出音频的语音转写

下面逐字段展开。

判别字段type:消息路由的稳定锚点

type: 'serverContent';

type是唯一必填字段,且是字面量'serverContent'。它的作用体现在两处:

  • 消费端路由:SDK 内部(如 live-session-helpers.ts 的接收循环)以及开发者自己的receive()消费代码,都需要先检查message.type === 'serverContent'再进入分支处理。
  • 线协议还原:服务端原始消息本身并不带type字段,SDK 在 live-session.ts#L348-L360 中检测到serverContent键后,将其解包并补上type: 'serverContent'再产出:
if (LiveResponseType.SERVER_CONTENT in message) { const serverContent = ( message as { serverContent: Omit<LiveServerContent, 'type'> } ).serverContent; if (serverContent?.modelTurn?.parts) { serverContent.modelTurn.parts = serverContent.modelTurn.parts.map(assignPartType); } yield { type: 'serverContent', ...serverContent } as LiveServerContent; }

值得注意的是,SDK 在产出前会调用assignPartType为每个 Part 补齐type标签(如text、inlineData),这保证了后续处理modelTurn.parts时能安全地按类型分派。

modelTurn:模型本轮生成的内容载体

modelTurn?: Content;

modelTurn持有模型在当前对话轮次生成的内容,类型为Content(定义见 packages/ai/src/types/content.ts#L24-L27):

export interface Content { role: Role; parts: Part[]; }

parts是联合类型数组,可能包含TextPart、InlineDataPart、FunctionCallPart、FunctionResponsePart、FileDataPart等(见 content.ts#L34-L41)。在实时语音场景中,modelTurn最常见的形态是:

  • 音频块:InlineDataPart,其inlineData为mimeType: 'audio/pcm'的 Base64 编码 PCM 数据;
  • 文本片段:TextPart,当模型开启文字输出或语音与文字混合输出时出现。

音频增量如何被播放:AudioConversationRunner的实现印证

SDK 的AudioConversationRunner(语音会话运行器)在 live-session-helpers.ts#L318-L335 中演示了标准消费方式:

const serverContent = message as LiveServerContent; if (serverContent.interrupted) { this.interruptPlayback(); } const audioPart = serverContent.modelTurn?.parts.find( part => part?.type === 'inlineData' && part.inlineData?.mimeType?.startsWith('audio/') ); if (audioPart && audioPart.type === 'inlineData') { const audioData = Uint8Array.from( atob(audioPart.inlineData.data), c => c.charCodeAt(0) ).buffer; this.enqueueAndPlay(audioData); }

即:从modelTurn.parts中筛选出mimeType以audio/开头的inlineData部分,把 Base64 解码为ArrayBuffer后进入播放队列。队列以 24kHz 16-bit PCM 为输入,通过 Web Audio API 的AudioBufferSourceNode无缝衔接调度(见 live-session-helpers.ts#L147-L152 的注释与队列实现)。

对应的单元测试 packages/ai/src/methods/live-session-helpers.test.ts#L233-L254 直接构造了一条LiveServerContent消息来验证音频入队播放:

const serverMessage: LiveServerContent = { type: 'serverContent', modelTurn: { role: 'model', parts: [ { type: 'inlineData', inlineData: { mimeType: 'audio/pcm', data: '1111222233334444' } } ] } };

这印证了:一条serverContent消息 = 一个(或多个)内容 Part 的增量,多条消息拼接起来才构成完整的模型输出。

turnComplete:轮次结束判定

turnComplete?: boolean;
  • true:模型本轮(turn)已完整结束,客户端可以据此切换发言状态、展示"对方已说完"或开启新一轮输入;
  • undefined:本轮尚未结束,后续还会继续收到携带更多modelTurn内容的serverContent消息。

由于增量更新的特性,模型的一段完整回答会被拆成多条serverContent消息下发,因此必须等待turnComplete === true才能认定一段话说完。turnComplete与interrupted都是可选的布尔标志,其"未触发即为undefined"的语义(而非false)是官方 API 文档明确强调的要点,消费时建议使用真值判断(if (msg.turnComplete))而非=== true之外的严格比较。

interrupted:客户端抢占导致的打断信号

interrupted?: boolean;

当客户端在模型尚未说完时抢先发送了新消息(文本、音频或视频输入),模型会被打断,服务端随后下发一条interrupted: true的serverContent消息。官方文档原话:"An interruption occurs when the client sends a message before the model finishes its turn."

AudioConversationRunner对该信号的处理非常典型(live-session-helpers.ts#L228-L243):

private interruptPlayback(): void { // 停止所有已调度的音频源 [...this.scheduledSources].forEach(source => source.stop(0)); // 清空尚未播放的音频缓冲 this.playbackQueue.length = 0; // 重置播放时钟,从当前时刻重新开始 this.nextStartTime = this.deps.audioContext.currentTime; }

也就是说,收到interrupted后 SDK 会立即停掉正在播放与排队等待的模型音频,避免"用户抢话后模型声音还在继续"的体验问题。测试 live-session-helpers.test.ts#L286-L320 精确验证了这一行为:先入队两个音频块,再下发{ type: 'serverContent', interrupted: true },断言两个AudioBufferSourceNode均被stop调用。

开发提示:如果你自研 UI 层,遇到interrupted === true时应同步清除本地"正在播放/正在显示"的模型内容缓冲,并立即允许用户继续输入。

inputTranscription/outputTranscription:双向语音转写

inputTranscription?: Transcription; outputTranscription?: Transcription;

两个字段分别承载输入给模型的音频与模型输出音频的文本转写,类型均为Transcription(定义见 packages/ai/src/types/responses.ts#L619-L624):

export interface Transcription { text?: string; }

目前Transcription仅含一个可选的text字段,即转写出的文本。

如何开启转写:LiveGenerationConfig

转写不是默认开启的,需要通过LiveGenerationConfig上的两个配置项启用(定义见 packages/ai/src/types/requests.ts#L301-L317):

  • inputAudioTranscription?: AudioTranscriptionConfig:开启后,模型会在LiveServerContent.inputTranscription中回传你语音输入的转写;
  • outputAudioTranscription?: AudioTranscriptionConfig:开启后,模型会在LiveServerContent.outputTranscription中回传其语音输出的转写。

官方文档特别提醒:转写是跨消息分片的。例如你对模型说 "How are you today?",转写可能分三条消息下发为 "How a"、"re yo"、"u today?";模型的语音输出同理。因此做字幕/转写展示时,必须在会话层面对连续多条消息的inputTranscription/outputTranscription做增量拼接,而不是把每条消息当成完整句子。

配置在协议层的搬运细节

一个值得注意的实现细节:inputAudioTranscription/outputAudioTranscription虽在公共 API 上属于LiveGenerationConfig,但后端期望它们位于 WebSocket 首条setup消息的顶层。SDK 在 packages/ai/src/models/live-generative-model.ts#L87-L110 的connect()中专门做了拆解与搬运:

const { inputAudioTranscription, outputAudioTranscription, ...generationConfig } = this.generationConfig; // ... const setupMessage: _LiveClientSetup = { setup: { model: fullModelPath, generationConfig, tools: this.tools, systemInstruction: this.systemInstruction, inputAudioTranscription, outputAudioTranscription, sessionResumption } };

同时类型层用_LiveGenerationConfig = Omit<LiveGenerationConfig, 'inputAudioTranscription' | 'outputAudioTranscription'>强制保证这两个字段不会泄漏到generationConfig中(见 packages/ai/src/types/live-responses.ts#L93-L101)。对应测试 packages/ai/src/models/live-generative-model.test.ts#L170-L190 也验证了setup.inputAudioTranscription/setup.outputAudioTranscription会被正确下发。

实战:消费LiveServerContent的完整模式

综合以上分析,一个标准的serverContent消费循环可以这样写(基于LiveSession.receive()的公开 API 形态):

const liveModel = new LiveGenerativeModel({ model: 'gemini-live-2.5-flash' }); const session = await liveModel.connect(); let inputCaption = ''; let outputCaption = ''; for await (const message of session.receive()) { if (message.type !== 'serverContent') { // 其它类型:toolCall / toolCallCancellation / goingAwayNotice / sessionResumptionUpdate continue; } const serverContent = message as LiveServerContent; // 1) 模型被客户端打断:清空本地正在播放/展示的模型内容 if (serverContent.interrupted) { stopLocalPlayback(); // 你的自定义实现 outputCaption = ''; continue; } // 2) 模型内容增量:文本直接展示,音频交给播放器 for (const part of serverContent.modelTurn?.parts ?? []) { if (part.type === 'text') { appendToModelTranscript(part.text); } else if (part.type === 'inlineData') { // mimeType 以 audio/ 开头时按音频处理 enqueueAudioForPlayback(part.inlineData.data); } } // 3) 转写增量:注意跨消息拼接 if (serverContent.inputTranscription?.text) { inputCaption += serverContent.inputTranscription.text; } if (serverContent.outputTranscription?.text) { outputCaption += serverContent.outputTranscription.text; } // 4) 轮次结束:刷新 UI 状态,允许用户发言 if (serverContent.turnComplete) { onModelTurnComplete(); } }

要点回顾:

  • 先以type分流,再做字段级处理;
  • interrupted优先处理,并清空本地缓冲;
  • 转写要拼接、modelTurn要按 Part 类型分派;
  • 以turnComplete作为一轮对话的结束信号。

使用前提与 API 状态说明

  • LiveServerContent属于Public Preview(@beta)API,官方在 docs-devsite/ai.liveservercontent.md 中明确提示:"Do not use this API in a production environment"(不要在生产环境使用),接口可能在后续版本中依据反馈调整。
  • 该接口随@firebase/ai包提供,源码位于 packages/ai/src/types/responses.ts#L580-L609;与之配套的接口还有 LiveSession、LiveGenerativeModel、LiveGenerationConfig、Content 与 Transcription。
  • 相关单元测试(live-session-helpers.test.ts 与 live-generative-model.test.ts)覆盖了音频入队播放、打断清队、转写配置下发等关键路径,可作为理解协议行为的参考实现。

小结

LiveServerContent是 Firebase JS SDK 实时语音对话的核心消息协议:modelTurn承载模型内容增量,turnComplete标记轮次边界,interrupted驱动打断处理,inputTranscription/outputTranscription提供可选的语音转写。掌握它的字段语义与增量特性,就能在自研的实时对话应用中正确处理语音播放、字幕拼接与交互状态机。

  • 开发工具
  • 后端

【免费下载链接】firebase-js-sdk

Firebase Javascript SDK

项目地址:https://gitcode.com/gh_mirrors/fi/firebase-js-sdk
点击查看免费下载
上一篇:终极指南:揭开Discord服务器隐藏频道的秘密 - ShowHiddenChannels完全解析
下一篇:中兴光猫工厂模式一键解锁:zteOnu工具完整使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进