> Discover all available pages from the documentation index: https://mastra.zisheng.pro/zh-HK/llms.txt # 即時語音 即時語音可將 Mastra Agent 變成用戶能在瀏覽器或電話上隨時插話的即時通話。Mastra 以開源的即時音訊及視訊 WebRTC 平台 [LiveKit](https://livekit.io) 建構這項功能。 [`@mastra/livekit`](https://mastra.zisheng.pro/zh-HK/reference/voice/livekit) 套件會將 Mastra Agent 連接至 [LiveKit Agents framework](https://docs.livekit.io/agents/):LiveKit 負責音訊循環,包括語音活動偵測、串流語音轉文字、語意輪次偵測、插話及文字轉語音。你的 Mastra Agent 則使用本身的模型、Tool 及 Memory 產生每個回應。 需要低延遲、可中斷的語音對話時,請使用即時語音。如要使用不依賴 LiveKit、以 Provider 為基礎的語音對語音功能,請參閱[語音對語音](https://mastra.zisheng.pro/zh-HK/guides/voice/speech-to-speech)。 ## 快速開始 以下步驟會由空白項目開始,建立一個可對話的語音 Agent。語音 session 包含兩個需要在此設定的運作部分:Mastra 伺服器上用來發出存取 token 的 API route,以及執行音訊 pipeline 並在每個輪次呼叫 Agent 的獨立 worker process。 1. 安裝整合套件,以及用於語音活動偵測及輪次偵測的 LiveKit plugin: **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 instance 加入語音 Agent,並公開連線 route。`liveKitConnectionRoute()` helper 會加入 `POST /voice/livekit/connection-details` endpoint,以簽發 LiveKit token,並將 Agent dispatch 至 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。它會以獨立 process 執行、回應 LiveKit session,並在每個輪次呼叫你的 Agent。Worker API 位於 `@mastra/livekit/worker` entry point,因此 Mastra 伺服器不會載入 LiveKit Agents runtime。這個範例使用 LiveKit Inference 模型字串進行語音轉文字及文字轉語音,無需安裝 Provider plugin: ```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` 選項會選擇回應每個 session 的 Mastra Agent。你可以像範例般傳入固定 key,或省略此選項以使用 dispatch metadata 中的 `agentId`,讓一個 worker 為 Mastra instance 上的所有 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 伺服器註冊並等候 session,而 `mastra dev` 則提供連線 route。 6. 與 Agent 對話。開啟託管的 [LiveKit Agents Playground](https://agents-playground.livekit.io) 並連接至你的項目,便可在無需建構前端的情況下開始通話。 如要改為連接自己的應用程式,請呼叫連線 route 取得 token。`POST /voice/livekit/connection-details` 會接受 request body 中選填的 `agentId`、`threadId` 及 `resourceId` 欄位,並傳回: ```json { "serverUrl": "wss://your-project.livekit.cloud", "roomName": "mastra-voice-a1b2c3d4", "participantName": "user-1", "participantToken": "eyJhbGci..." } ``` 此回應符合 LiveKit 前端 starter 使用的 contract,因此以 [agent-starter-react](https://github.com/livekit-examples/agent-starter-react) 或 [LiveKit React components](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 的語意輪次結束模型。它會讀取即時轉錄,避免在用戶尚未表達完整意思時打斷對方。如要改用以靜音為基礎的 endpointing,請使用 `'vad'` 或 `'stt'`。 - `endpointing`:限制用戶停止說話後 Agent 的等候時間。 - `interruption`:控制插話。當用戶在 Agent 說話期間發言,LiveKit 會停止播放並取消進行中的 Mastra stream,因此 token 產生亦會停止。 - `preemptiveGeneration`:在用戶仍在結束發言時開始產生 Mastra Agent 的回應,藉此隱藏首個 token 的等候時間。Worker 預設停用此功能:每次預先產生嘗試都會使用暫時轉錄執行 Mastra Agent,而且每次執行都會保存用戶訊息,令 thread 出現重複訊息。如果延遲比精確的 thread 歷史記錄更重要,可使用 `preemptiveGeneration: { enabled: true }` 重新啟用。 如要了解所有選項,請參閱 [LiveKit 輪次偵測文件](https://docs.livekit.io/agents/logic/turns/)。 ## 每次通話的聲線及轉錄 頂層 `stt` 及 `tts` 選項會套用至每次通話。如要按通話選擇,為每個 tenant 指定一種聲線或語言,請改為設定 `configuration.stt` 及 `configuration.tts` resolver。每個 resolver 在每次通話中執行一次,並接收 dispatch metadata、request context、room 名稱及 job context,再傳回相應頂層選項所接受的值。該值可以是 plugin instance 或 inference 模型字串。傳回 `undefined` 則會使用頂層選項作為 fallback。 以下範例按照 dispatch metadata 中的 `tenant` 項目,為每個 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` 以相同方式為每次通話設定轉錄,例如為每個 tenant 指定不同的轉錄模型或語言。問候語亦有相應的按通話形式:`configuration.greeting.text` 接受具有相同 call context 的 resolver,讓一個 worker 可使用各 tenant 專屬的措辭開始對話。 ## 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 會建立標題為「語音通話」、metadata 為 `{ source: 'livekit' }` 的 thread,並將播放的問候語儲存為第一則 assistant 訊息,令 thread 可作為完整通話轉錄閱讀(可使用 `persistGreeting: false` 停用)。 每個輪次只會傳送新的用戶輸入;Mastra Memory 會提供歷史記錄、語意回憶及工作記憶。你可以在連線 request body 傳入 `threadId`,將 session 固定至現有 thread,這對以語音繼續文字對話十分有用。在 Studio 中,從已開啟的對話開始通話會將該通話綁定至該 thread,而每次交流後,轉錄內容都會填入對話。 當用戶打斷 Agent,進行中的產生程序會中止,該輪次當下不會保存任何內容。LiveKit 會在轉錄中保留用戶實際聽到的部分;下一個輪次時,worker 會重新傳送這段只包含已聽內容的片段,讓 thread 補回內容以配合通話。如果用戶在打斷後立即掛線,最後一段片段便不會記錄。如要了解詳情及協調處理方法,請參閱[已中斷的輪次](https://mastra.zisheng.pro/zh-HK/reference/voice/livekit)。 ## Tool 執行期間播放語音 語音對話不能在執行緩慢的 Tool 時陷入靜默。Mastra Agent 開始 Tool call 時,可使用 `toolFeedback` 播放一句簡短說話: ```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/zh-HK/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 串流的是結構化 step event,而非文字。如要在 token 產生時播放語音,reply step 需將其 Agent 的文字 pipe 至該 step 的 `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`:將語音輸出限制在一個 step。省略此選項,即會播放每個寫入其 `writer` 的 step。 - `resultText`:當沒有 step 串流文字時,從最終執行結果衍生回應的 fallback。透過 `writer` 串流可縮短首個 token 的等候時間,因此應優先使用。 - `abortSignal`:將 step 的 `abortSignal` 轉交至 `agent.stream()`,讓插話能迅速停止產生內容。用戶插話時,worker 會取消執行。 - `generate`:如要完全控制,可改為傳入 `generate` function。它可以是任何將一個輪次轉換成文字 stream 的回應 generator。 使用 Workflow 時,worker 不會像 Agent 的 `stream()` 般自動保存輪次。請在 Workflow 內保存對話歷史記錄,或以 LiveKit 轉錄作為真實資料來源,並在每個輪次傳入。 ## 將 Mastra 用作 LLM 元件 `createLiveKitWorker()` 會代你管理 LiveKit session。如要自行管理 session,請改用 [`MastraLLM`](https://mastra.zisheng.pro/zh-HK/reference/voice/livekit):這是標準 LiveKit LLM plugin,會將 Mastra Agent 放入 `llm` slot,而該 slot 位於你自己的 `voice.AgentSession`。Mastra 應用程式、Agent loop、Tool、Memory 及可觀測性均在 Mastra 伺服器上執行,worker 則透過 HTTP 存取。Worker process 不需要 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' }) } ``` 兩種方式在底層共用相同的回應 pipeline;請按哪一方應管理 session 作出選擇: | | `createLiveKitWorker()` | `MastraLLM` | | ------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------- | | Session 管理權 | Worker helper 建構並管理 `AgentSession` | 由你的程式碼建構 session;所有 LiveKit 選項及 hook 均由你控制 | | Mastra 應用程式執行位置 | Worker process 內 | Mastra 伺服器上,透過 HTTP 存取(或透過 `agent` 在 process 內存取) | | Worker process 所需項目 | Mastra 應用程式、儲存空間及模型 Provider 金鑰 | 只需 LiveKit SDK 及連接伺服器的網絡存取權 | | 內置便利功能 | 問候語、同意閘控、由 Agent 主動掛線、thread bootstrap、可觀測性匯總 | 使用 [session helper](https://mastra.zisheng.pro/zh-HK/reference/voice/livekit) 重新建構所需功能 | | 最適合 | 最快速建立可運作語音 Agent 的方式;Studio 語音模式 | 現有 LiveKit 應用程式及完整 session 控制權 | Tool 會保留在 Mastra Agent 上並於伺服器執行。傳入 session 的 LiveKit 端 Tool 會被忽略。Tool 活動會透過 `toolFeedback`(播放的填充語句)、`onToolCall`(每次 Tool call 開始時觸發)及 `onTurnComplete`(每次回應後觸發,並附帶文字、Tool call 及 token 用量)傳至 worker。由 Agent 主動掛線只需數行程式碼:將 `onToolCall` 與 [`runEndCall()`](https://mastra.zisheng.pro/zh-HK/reference/voice/livekit) 配合使用。 > **注意:** 請勿將 `memory` 選項與 LiveKit 的 `preemptiveGeneration` 一同使用;在你自行建構的 session 中,LiveKit 預設會啟用後者。如果推測輪次在 LiveKit 捨棄之前已完成,便會將一則用戶訊息及從未播放的回應保存至 thread。請設定 `turnHandling: { preemptiveGeneration: { enabled: false } }`,或不使用 `memory`,並在每個輪次傳入完整轉錄。 `MastraLLM` 亦接受 process 內的 Mastra `agent` instance,讓你無需第二次部署即可管理 session;它也接受自訂 `generate` function。Remote transport 可透過 [`createRemoteAgentReplyGenerator()`](https://mastra.zisheng.pro/zh-HK/reference/voice/livekit) 獨立使用,亦可接入 `createLiveKitWorker` 的 `generate` 選項,以功能齊全的 worker 連接遠端伺服器。 ## 由伺服器啟動的 session 使用 `dispatchVoiceSession()`,可從你自己的程式碼將語音 Agent 加入 room,例如加入現有 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 instance 已設定[可觀測性](https://mastra.zisheng.pro/zh-HK/docs/observability/overview),worker 會為每次通話建立 Trace。它會為每個 session 開啟一個 `voice call` span,並將所有內容置於其下: - 每個輪次的 Mastra Agent 執行,包括模型產生、Tool call 及 Memory 操作,記錄方式與文字對話完全相同。 - 每項 LiveKit pipeline 指標的 child span:語音轉文字、文字轉語音、語句結束(輪次偵測)、語音活動偵測,以及模型產生首個 token 的時間。這些 span 包含文字 Trace 無法顯示的延遲及音訊測量資料。 - 按模型匯總的用量(整次通話的 token、字元及音訊總量),並在 session 結束時寫入 span。 Worker 是獨立 process,因此請將儲存空間指向可同時接受伺服器及 worker 寫入的後端。以 SQLite 為基礎的 [LibSQL](https://mastra.zisheng.pro/zh-HK/reference/storage/libsql) 可以使用,只支援單一 writer 的儲存空間則不可以。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()], }, }, }), }) ``` Tracing 預設啟用。傳入 `observability: false` 至 `createLiveKitWorker` 即可停用。 ## 部署 Worker 與 Mastra 伺服器是不同的 process,因此 `mastra build` 需要將它輸出為獨立 entry。請將它加入 [`bundler.entries`](https://mastra.zisheng.pro/zh-HK/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` 現在會將兩個 process 寫入 `.mastra/output`,並共用一個 `package.json` 及一組已安裝的依賴套件: ```text .mastra/output/ index.mjs # Mastra server voice-worker.mjs # LiveKit worker ``` 將該目錄作為單一 artifact 部署,並以各自的命令啟動每個 process: ```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,因此不需要 inbound port。 ## 運作方式 LiveKit 語音 session 包含三個部分: 1. Mastra 伺服器簽發 LiveKit 存取 token,並將 Agent dispatch 至 room。Dispatch 會攜帶 Mastra Agent ID、Memory thread 及 resource 等 metadata。 2. LiveKit Agent worker(獨立且長時間執行的 process)接收 job 並執行音訊 pipeline。音訊經 WebRTC 在瀏覽器與 worker 之間傳送,絕不會經過 Mastra HTTP 伺服器。 3. 每當用戶完成一個輪次,worker 都會使用新輸入呼叫 Mastra Agent 的 `stream()`,並播放串流文字。用戶插話時,LiveKit 會取消 stream,而 Mastra 會停止產生內容。 對話歷史記錄儲存在 Mastra Memory,因此語音 session 及文字對話可以共用同一個 thread。 ## 相關內容 - [`@mastra/livekit` 參考資料](https://mastra.zisheng.pro/zh-HK/reference/voice/livekit) - [語音對語音](https://mastra.zisheng.pro/zh-HK/guides/voice/speech-to-speech) - [Agent Memory](https://mastra.zisheng.pro/zh-HK/docs/memory/overview) - [LiveKit Agents 文件](https://docs.livekit.io/agents/)