- 开发工具
- 后端
【免费下载链接】firebase-js-sdk
Firebase Javascript 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等其它消息 |
modelTurn | Content | 模型在本轮对话中生成的内容(文本、内联音频等 Part 集合) |
turnComplete | boolean | 是否已到模型回合结束;未结束时为undefined |
interrupted | boolean | 模型是否被客户端打断;未被中断时为undefined |
inputTranscription | Transcription | 输入给模型的音频的语音转写 |
outputTranscription | Transcription | 模型输出音频的语音转写 |
下面逐字段展开。
判别字段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
相关推荐
Firebase JS SDK 中 LanguageModelCreateCoreOptions 详解:配置 Chrome 端侧语言模型会话的入口
Firebase JS SDK 中 LanguageModelCreateCoreOptions 详解:配置 Chrome 端侧语言模型会话的入口 在 Fire
开发工具后端Firebase JS SDK AI 实时语音对话控制器 AudioConversationController 深入解析
Firebase JS SDK AI 实时语音对话控制器 AudioConversationController 深入解析 Firebase JS SDK 在
开发工具后端LanceDB JS SDK `SessionLogout` 接口详解:OAuth 会话登出的返回值与幂等语义
LanceDB JS SDK SessionLogout 接口详解:OAuth 会话登出的返回值与幂等语义 导读 SessionLogout 是 LanceDB
数据库向量数据库全文检索人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考