> Discover all available pages from the documentation index: https://mastra.zisheng.pro/llms.txt # 实时语音 实时语音可将 Mastra Agent 变成用户能在浏览器或电话中随时插话的实时通话。Mastra 基于开源实时音视频 WebRTC 平台 [LiveKit](https://livekit.io) 构建此功能。 [`@mastra/livekit`](https://mastra.zisheng.pro/reference/voice/livekit) 软件包将 Mastra Agent 连接到 [LiveKit Agents 框架](https://docs.livekit.io/agents/):LiveKit 负责语音活动检测、流式语音转文本、语义轮次检测、插话和文本转语音等音频循环;Mastra Agent 则使用自己的模型、Tool 和 memory 生成每条回复。 需要低延迟、可打断的语音对话时,请使用实时语音。若要使用不依赖 LiveKit、基于 Provider 的语音转语音,请参阅[语音转语音](https://mastra.zisheng.pro/guides/voice/speech-to-speech)。 ## 快速入门 以下步骤将从空项目开始,构建一个可与之交谈的语音 Agent。这里需要设置语音会话的两个部分:Mastra 服务器上分发访问 token 的 API 路由,以及运行音频管道并在每个轮次调用 Agent 的独立 worker 进程。 1. 安装集成软件包,以及用于语音活动检测和轮次检测的 LiveKit 插件: **npm**: ```bash npm install @mastra/livekit @livekit/agents @livekit/agents-plugin-silero @livekit/agents-plugin-livekit ``` **pnpm**: ```bash pnpm add @mastra/livekit @livekit/agents @livekit/agents-plugin-silero @livekit/agents-plugin-livekit ``` **Yarn**: ```bash yarn add @mastra/livekit @livekit/agents @livekit/agents-plugin-silero @livekit/agents-plugin-livekit ``` **Bun**: ```bash bun add @mastra/livekit @livekit/agents @livekit/agents-plugin-silero @livekit/agents-plugin-livekit ``` 2. 在 `.env` 文件中设置 LiveKit 凭据。在 [LiveKit Cloud](https://cloud.livekit.io) 创建免费项目,或使用 [`livekit-server --dev`](https://docs.livekit.io/home/self-hosting/local/) 运行本地服务器: ```bash LIVEKIT_URL=wss://your-project.livekit.cloud LIVEKIT_API_KEY=your-api-key LIVEKIT_API_SECRET=your-api-secret ``` 3. 向 Mastra 实例添加语音 Agent 并公开连接路由。`liveKitConnectionRoute()` helper 会添加 `POST /voice/livekit/connection-details` 端点,用于签发 LiveKit token 并将 Agent 分派到 room: ```typescript import { Mastra } from '@mastra/core/mastra' import { Agent } from '@mastra/core/agent' import { liveKitConnectionRoute } from '@mastra/livekit' const supportAgent = new Agent({ id: 'support', name: 'Support', instructions: 'You are a friendly phone support agent. Keep replies short and conversational.', model: 'openai/gpt-5-mini', }) export const mastra = new Mastra({ agents: { support: supportAgent }, server: { apiRoutes: [liveKitConnectionRoute({ agentName: 'mastra-voice' })], }, }) ``` 4. 创建 worker。它作为独立进程运行,响应 LiveKit 会话,并在每个轮次调用 Agent。Worker API 位于 `@mastra/livekit/worker` 入口,因此 Mastra 服务器不会加载 LiveKit Agents 运行时。此示例将 LiveKit Inference 模型字符串用于语音转文本和文本转语音,因此无需 Provider 插件: ```typescript import { fileURLToPath } from 'node:url' import { createLiveKitWorker, runLiveKitWorker } from '@mastra/livekit/worker' import { mastra } from './index' export default createLiveKitWorker({ mastra, agent: 'support', stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', turnDetection: 'multilingual', greeting: 'Hi! How can I help you today?', }) if (process.argv[1] === fileURLToPath(import.meta.url)) { runLiveKitWorker({ entry: import.meta.url, agentName: 'mastra-voice' }) } ``` `agent` 选项用于选择由哪个 Mastra Agent 响应每个会话。可以像示例一样传入固定 key,也可以省略它以使用 dispatch metadata 中的 `agentId`,从而让一个 worker 服务 Mastra 实例上的所有 Agent。 5. 下载一次轮次检测和语音活动检测模型。然后在一个终端中运行 worker,在另一个终端中运行 Mastra 服务器: ```bash npx livekit-agents download-files npx tsx src/mastra/voice-worker.ts dev ``` **npm**: ```bash npm run dev ``` **pnpm**: ```bash pnpm run dev ``` **Yarn**: ```bash yarn dev ``` **Bun**: ```bash bun run dev ``` Worker 会向 LiveKit 服务器注册并等待会话,而 `mastra dev` 会提供连接路由。 6. 与 Agent 对话。打开托管的 [LiveKit Agents Playground](https://agents-playground.livekit.io) 并将其连接到项目,无需构建前端即可开始通话。 若要连接自己的应用,请调用连接路由获取 token。`POST /voice/livekit/connection-details` 在请求正文中接受可选的 `agentId`、`threadId` 和 `resourceId` 字段,并返回: ```json { "serverUrl": "wss://your-project.livekit.cloud", "roomName": "mastra-voice-a1b2c3d4", "participantName": "user-1", "participantToken": "eyJhbGci..." } ``` 此响应符合 LiveKit 前端 starter 使用的契约,因此基于 [agent-starter-react](https://github.com/livekit-examples/agent-starter-react) 或 [LiveKit React 组件](https://docs.livekit.io/reference/components/react/)构建的应用无需修改即可使用。 ## 轮次检测和打断 LiveKit 会判断用户何时说完以及 Agent 何时被打断。默认设置通常效果良好;可以通过 `turnHandling` 进行调整: ```typescript export default createLiveKitWorker({ mastra, agent: 'support', stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', turnDetection: 'multilingual', turnHandling: { endpointing: { mode: 'dynamic', minDelay: 300, maxDelay: 3000 }, interruption: { minDuration: 500, resumeFalseInterruption: true }, }, }) ``` - `turnDetection: 'multilingual'`:在本地 CPU 上运行 LiveKit 的语义轮次结束模型。它读取实时转录内容,避免在用户还没说完时将其打断。若要改用基于静音的端点检测,请使用 `'vad'` 或 `'stt'`。 - `endpointing`:限制用户停止说话后 Agent 的等待时间。 - `interruption`:控制插话。用户在 Agent 说话时插话,LiveKit 会停止播放并取消进行中的 Mastra 流,因此 token 生成也会停止。 - `preemptiveGeneration`:在用户即将说完时开始生成 Mastra Agent 的回复,以隐藏首个 token 延迟。Worker 默认禁用此功能:每次预生成尝试都会使用临时转录内容运行 Mastra Agent,而且每次运行都会持久化用户消息,导致 thread 中出现重复消息。如果延迟比精确的 thread 历史更重要,可通过 `preemptiveGeneration: { enabled: true }` 重新启用。 所有选项请参阅 [LiveKit 轮次检测文档](https://docs.livekit.io/agents/logic/turns/)。 ## 每次通话的语音和转录 顶层 `stt` 和 `tts` 选项适用于每次通话。若要按通话选择,例如为每个租户指定一种语音或语言,请改为设置 `configuration.stt` 和 `configuration.tts` resolver。每个 resolver 在每次通话中运行一次,可访问 dispatch metadata、请求上下文、room 名称和任务上下文,并返回对应顶层选项接受的值。该值可以是插件实例或 inference 模型字符串。返回 `undefined` 会回退到顶层选项。 以下示例根据 dispatch metadata 中的 `tenant` 条目,为每个租户提供自己的文本转语音语音: ```typescript import * as cartesia from '@livekit/agents-plugin-cartesia' // One voice id per tenant, resolved from the dispatch metadata on each call. const tenantVoices: Record = { meridian: 'your-cartesia-voice-id-1', coastal: 'your-cartesia-voice-id-2', } // The resolver runs during call setup, so cache plugin instances across calls. const ttsByVoice = new Map() export default createLiveKitWorker({ mastra, agent: 'support', stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', configuration: { tts: ({ requestContext }) => { const voice = tenantVoices[requestContext?.tenant as string] if (!voice) return undefined // fall back to the top-level `tts` let tts = ttsByVoice.get(voice) if (!tts) { tts = new cartesia.TTS({ voice }) ttsByVoice.set(voice, tts) } return tts }, }, }) ``` `configuration.stt` 以同样方式支持按通话转录,例如为每个租户使用不同的转录模型或语言。问候语也有对应的按通话形式:`configuration.greeting.text` 接受具有相同通话上下文的 resolver,因此一个 worker 可以使用各租户自己的措辞开场。 ## Memory 和 thread 解析出的 Mastra Agent 配置了 memory 时,每次通话都会成为一个 memory thread: - `thread` 默认使用 dispatch metadata 中的 `threadId`,其次使用 room 名称。 - `resource` 默认使用 dispatch metadata 中的 `resourceId`,其次使用 thread。请在这里发送最终用户 ID,以便将通话归到正确用户下。Mastra Studio 会发送 Agent ID,这与其侧边栏列出 thread 的方式一致。 - 如果 thread 尚不存在,worker 会创建一个标题为“Voice call”、metadata 为 `{ source: 'livekit' }` 的 thread,并将说出的问候语保存为第一条助手消息,使 thread 呈现完整的通话记录(可用 `persistGreeting: false` 禁用)。 每个轮次只发送新的用户输入;Mastra Memory 提供历史记录、语义召回和工作记忆。可以在连接请求正文中传入 `threadId`,将会话固定到现有 thread,这有助于通过语音继续文本对话。在 Studio 中,从打开的聊天开始通话会将通话绑定到该 thread,并在每次交流后将转录内容填入聊天。 用户打断 Agent 时,进行中的生成会中止,该轮次此刻不会持久化任何内容。LiveKit 会在转录中保留用户实际听到的部分;下一轮中,worker 会重新发送这段仅已播放的片段,让 thread 回填并与通话一致。如果用户打断后立即挂断,最后的片段不会被记录。详细信息和对账方案请参阅[被打断的轮次](https://mastra.zisheng.pro/reference/voice/livekit)。 ## Tool 运行时播报 运行缓慢 Tool 时,语音对话不能陷入沉默。使用 `toolFeedback`,在 Mastra Agent 开始调用 Tool 时播报简短语句: ```typescript export default createLiveKitWorker({ mastra, agent: 'support', stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', toolFeedback: ({ toolName }) => toolName === 'searchOrders' ? 'Let me look that up.' : undefined, }) ``` 该语句会作为回复的一部分播报并记录在转录中。 ## 使用 Workflow 生成回复 默认情况下,worker 使用 Mastra Agent 生成每条回复。若要在每轮运行多步骤逻辑(例如对意图分类、路由、按顺序调用 Tool,再组织回复),请改用 Mastra [Workflow](https://mastra.zisheng.pro/docs/workflows/overview) 生成回复。设置 `workflow` 代替 `agent`。 LiveKit 仍负责音频循环,并在每轮调用一次 Mastra,因此 Workflow 会在每轮运行至完成。Workflow 无法暂停或恢复,轮次之间也不会保留对话状态。通过 `workflowInput` 传入转录内容,使 Workflow 保持无状态: ```typescript import { createLiveKitWorker, chatContextToMessages } from '@mastra/livekit/worker' import { mastra } from './index' export default createLiveKitWorker({ mastra, workflow: 'phoneConversation', workflowInput: ({ chatCtx }) => ({ history: chatContextToMessages(chatCtx) }), replyStep: 'generateResponse', stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', turnDetection: 'multilingual', }) ``` Workflow 流式输出的是结构化步骤事件,而不是文本。要在 token 生成时将其播报,回复步骤需将 Agent 文本通过管道写入该步骤的 `writer`: ```typescript const generateResponse = createStep({ id: 'generateResponse', // input and output schemas omitted execute: async ({ inputData, mastra, writer, abortSignal }) => { const stream = await mastra.getAgent('voice').stream(inputData.history, { abortSignal }) await stream.textStream.pipeTo(writer) return { assistantMessage: await stream.text } }, }) ``` - `replyStep`:将播报输出限制在一个步骤。省略时,会播报所有写入其 `writer` 的步骤。 - `resultText`:当没有步骤流式输出文本时,从最终运行结果派生回复的回退方案。通过 `writer` 流式输出的首个 token 延迟更低,因此应优先使用。 - `abortSignal`:将步骤的 `abortSignal` 转发给 `agent.stream()`,使插话能及时停止生成。用户打断时,worker 会取消运行。 - `generate`:若要完全控制,请改为传入 `generate` 函数。它可以是任何将轮次转换为文本流的回复生成器。 使用 Workflow 时,worker 不会像 Agent 的 `stream()` 那样自动持久化轮次。请在 Workflow 内持久化对话历史,或将 LiveKit 转录作为事实来源并在每轮传入。 ## 将 Mastra 用作 LLM 组件 `createLiveKitWorker()` 会代你管理 LiveKit 会话。若要自行管理会话,请改用 [`MastraLLM`](https://mastra.zisheng.pro/reference/voice/livekit):这是一个标准 LiveKit LLM 插件,可将 Mastra Agent 放入自有 `voice.AgentSession` 的 `llm` 插槽。Mastra 应用、Agent 循环、Tool、memory 和可观测性都在 Mastra 服务器上运行,worker 通过 HTTP 访问它。Worker 进程不需要 Mastra 应用、数据库或模型 Provider 密钥。 ```typescript import { fileURLToPath } from 'node:url' import { defineAgent, voice } from '@livekit/agents' import * as silero from '@livekit/agents-plugin-silero' import { MastraLLM } from '@mastra/livekit/plugin' import { runLiveKitWorker } from '@mastra/livekit/worker' export default defineAgent({ entry: async ctx => { await ctx.connect() const session = new voice.AgentSession({ llm: new MastraLLM({ remote: { baseUrl: process.env.MASTRA_URL!, agentId: 'support' }, memory: { thread: ctx.room.name!, resource: 'user-7' }, }), stt: 'deepgram/nova-3', tts: 'cartesia/sonic-3', vad: await silero.VAD.load(), // Required with `memory`: LiveKit enables preemptive generation by default. turnHandling: { preemptiveGeneration: { enabled: false } }, }) await session.start({ // These instructions never reach the Mastra agent; its own instructions apply. agent: new voice.Agent({ instructions: 'Replies come from the Mastra agent.' }), room: ctx.room, }) session.say('Hi! How can I help you today?') }, }) if (process.argv[1] === fileURLToPath(import.meta.url)) { runLiveKitWorker({ entry: import.meta.url, agentName: 'mastra-voice' }) } ``` 两种方式在底层共享同一回复管道;请根据会话应由谁管理来选择: | | `createLiveKitWorker()` | `MastraLLM` | | ------------- | ------------------------------------ | ------------------------------------------------------------------------- | | 会话管理方 | Worker helper 构建并管理 `AgentSession` | 你的代码构建会话;可以控制每个 LiveKit 选项和 hook | | Mastra 应用运行位置 | Worker 进程中 | Mastra 服务器上,通过 HTTP 访问(或通过 `agent` 在进程内访问) | | Worker 进程需要 | Mastra 应用、存储和模型 Provider 密钥 | 仅需要 LiveKit SDK 和访问服务器的网络连接 | | 内置便利功能 | 问候语、同意门控、Agent 主动挂断、thread 引导和可观测性汇总 | 使用[会话 helper](https://mastra.zisheng.pro/reference/voice/livekit)重新构建所需功能 | | 最适合 | 最快构建可用语音 Agent;Studio 语音模式 | 现有 LiveKit 应用以及完全控制会话 | Tool 保留在 Mastra Agent 上并在服务器执行。传给会话的 LiveKit 侧 Tool 会被忽略。Tool 活动通过 `toolFeedback`(播报填充语)、`onToolCall`(每次 Tool 调用开始时触发)和 `onTurnComplete`(每次回复后触发,并提供文本、Tool 调用和 token 用量)传给 worker。Agent 主动挂断只需几行代码:将 `onToolCall` 与 [`runEndCall()`](https://mastra.zisheng.pro/reference/voice/livekit) 配合使用。 > **注意:** 不要将 `memory` 选项与 LiveKit 的 `preemptiveGeneration` 结合使用;在自行构建的会话中,LiveKit 默认启用后者。如果推测轮次在 LiveKit 丢弃它之前完成,用户消息和尚未播报的回复都会持久化到 thread。请设置 `turnHandling: { preemptiveGeneration: { enabled: false } }`,或在不使用 `memory` 的情况下运行,并在每轮传入完整转录内容。 `MastraLLM` 也接受进程内 Mastra `agent` 实例,从而无需第二次部署即可管理会话;它还接受自定义 `generate` 函数。远程传输可通过 [`createRemoteAgentReplyGenerator()`](https://mastra.zisheng.pro/reference/voice/livekit) 独立使用,也可接入 `createLiveKitWorker` 的 `generate` 选项,让功能完备的 worker 对接远程服务器。 ## 服务器发起的会话 使用 `dispatchVoiceSession()` 从自己的代码向 room 添加语音 Agent,例如加入现有 room 或驱动外呼 [SIP 通话](https://docs.livekit.io/sip/): ```typescript import { dispatchVoiceSession } from '@mastra/livekit' await dispatchVoiceSession({ roomName: 'support-call-42', agentName: 'mastra-voice', metadata: { agentId: 'support', threadId: 'thread-42', resourceId: 'user-7' }, }) ``` ## 可观测性 Mastra 实例配置了[可观测性](https://mastra.zisheng.pro/docs/observability/overview)后,worker 会追踪每次通话。它为每个会话打开一个 `voice call` span,并将所有内容嵌套在其中: - 每轮 Mastra Agent 运行,包括模型生成、Tool 调用和 memory 操作,记录方式与文本聊天完全相同。 - 为每项 LiveKit 管道指标创建子 span:语音转文本、文本转语音、话语结束(轮次检测)、语音活动检测以及模型的首个 token 延迟。这些 span 包含文本 Trace 无法显示的延迟和音频测量值。 - 会话结束时写入 span 的按模型用量汇总(整次通话的 token、字符和音频总量)。 Worker 是独立进程,因此请将存储指向可接受服务器和 worker 并发写入的后端。基于 SQLite 的 [LibSQL](https://mastra.zisheng.pro/reference/storage/libsql) 可以使用,单写入方存储则不行。Trace、memory 和 thread 可以共享一个存储: ```typescript import { Mastra } from '@mastra/core/mastra' import { LibSQLStore } from '@mastra/libsql' import { Observability, MastraStorageExporter } from '@mastra/observability' export const mastra = new Mastra({ storage: new LibSQLStore({ id: 'voice-agent-storage', url: 'file:./voice-agent.db' }), observability: new Observability({ configs: { default: { serviceName: 'voice-agent', exporters: [new MastraStorageExporter()], }, }, }), }) ``` 默认启用追踪。向 `createLiveKitWorker` 传入 `observability: false` 可将其关闭。 ## 部署 Worker 与 Mastra 服务器是不同进程,因此 `mastra build` 需要将其作为独立入口输出。将它添加到 [`bundler.entries`](https://mastra.zisheng.pro/reference/configuration): ```typescript import { Mastra } from '@mastra/core' export const mastra = new Mastra({ bundler: { entries: { 'voice-worker': './voice-worker.ts' }, // Keep LiveKit's native modules out of the bundle. `mastra build` only applies // this default when you set no other bundler options, so set it explicitly here. externals: true, }, }) ``` 现在,`mastra build` 会将两个进程都写入 `.mastra/output`,共享一个 `package.json` 和一次依赖安装: ```text .mastra/output/ index.mjs # Mastra server voice-worker.mjs # LiveKit worker ``` 将该目录部署为单个构件,并使用各自的命令启动每个进程: ```bash node .mastra/output/index.mjs # server node .mastra/output/voice-worker.mjs start # worker ``` Worker 需要与服务器相同的环境变量,另外还需要 `LIVEKIT_URL`、`LIVEKIT_API_KEY` 和 `LIVEKIT_API_SECRET`。 LiveKit 关于容量规划、优雅关闭和托管的指导仍然适用。请参阅[部署 Agent](https://docs.livekit.io/agents/ops/deployment/)。Worker 会向外连接 LiveKit,因此不需要入站端口。 ## 工作原理 LiveKit 语音会话包括三个部分: 1. Mastra 服务器签发 LiveKit 访问 token,并将 Agent 分派到 room。分派中包含 Mastra Agent ID、memory thread 和 resource 等 metadata。 2. LiveKit Agent worker(独立的长期运行进程)接收任务并运行音频管道。音频通过 WebRTC 在浏览器与 worker 之间流动,绝不会经过 Mastra HTTP 服务器。 3. 每当用户结束一轮,worker 都会使用新输入调用 Mastra Agent 的 `stream()` 并播报流式文本。用户打断时,LiveKit 会取消流,Mastra 也会停止生成。 对话历史保存在 Mastra Memory 中,因此语音会话和文本聊天可以共享同一个 thread。 ## 相关内容 - [`@mastra/livekit` 参考](https://mastra.zisheng.pro/reference/voice/livekit) - [语音转语音](https://mastra.zisheng.pro/guides/voice/speech-to-speech) - [Agent Memory](https://mastra.zisheng.pro/docs/memory/overview) - [LiveKit Agents 文档](https://docs.livekit.io/agents/)