> Discover all available pages from the documentation index: https://mastra.zisheng.pro/zh-TW/llms.txt # 即時語音 即時語音可將 Mastra Agent 轉換成使用者能在瀏覽器或電話上交談並隨時插話的即時通話。Mastra 以開放原始碼的即時音訊與視訊 WebRTC 平台 [LiveKit](https://livekit.io) 為基礎建構此功能。 [`@mastra/livekit`](https://mastra.zisheng.pro/zh-TW/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-TW/guides/voice/speech-to-speech)。 ## 快速入門 以下步驟會從空白專案開始,建立可與之交談的 Voice Agent。語音工作階段包含兩個需要設定的部分:在 Mastra 伺服器上發放存取 token 的 API 路由,以及執行音訊 pipeline 並在每個回合呼叫 Agent 的獨立 worker 處理程序。 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. 將 Voice Agent 加入 Mastra 執行個體,並公開連線路由。`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。它會以獨立處理程序執行、回應 LiveKit 工作階段,並在每個回合呼叫 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` 選項會選擇要由哪個 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` 的 request body 可接受選用的 `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 的語意回合結束模型。它會讀取即時轉錄內容,避免在使用者尚未說完時打斷。若要改用以靜音為基礎的 endpointing,請使用 `'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` 選項會套用至每通電話。若要為每通電話選擇設定,例如為各 tenant 指定一種聲音或語言,請改為設定 `configuration.stt` 與 `configuration.tts` resolver。每個 resolver 都會在每通電話中執行一次,並接收 dispatch metadata、request context、room 名稱與 job context,再傳回對應頂層選項可接受的值。此值可以是 plugin 執行個體或 inference 模型字串。傳回 `undefined` 即會改用頂層選項。 下列範例會依據 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` 接受具有相同通話 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 會提供歷史記錄、語意回憶與 working memory。若要將工作階段固定至現有 thread,請在連線要求的 body 中傳入 `threadId`;需要透過語音延續文字對話時,這項功能相當實用。在 Studio 中,從已開啟的聊天開始通話會將該通話繫結至該 thread,並在每次對話後將轉錄內容填入聊天。 當使用者打斷 Agent 時,執行中的產生作業會中止,當下不會保存該回合的任何內容。LiveKit 會在轉錄內容中保留使用者實際聽到的部分;下一個回合中,worker 會重新傳送這段只有使用者聽到的內容,讓 thread 回填成與通話一致。如果使用者在打斷後立刻掛斷,最後這段內容便不會留下記錄。如需詳細資訊與資料校正方式,請參閱[遭到中斷的回合](https://mastra.zisheng.pro/zh-TW/reference/voice/livekit)。 ## Tool 執行時播放語音 執行速度較慢的 Tool 時,語音對話不能長時間沉默。當 Mastra Agent 開始呼叫 Tool 時,請使用 `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-TW/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 產生時直接播放,回應 step 必須將其 Agent 的文字透過管線傳入該 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 串流文字時,從最終執行結果取得回應的備援方式。透過 `writer` 串流可縮短第一個 token 的等待時間,因此建議優先使用。 - `abortSignal`:將 step 的 `abortSignal` 傳入 `agent.stream()`,讓插話能立即停止產生內容。使用者插話時,worker 會取消該次執行。 - `generate`:若要完全掌控行為,請改為傳入 `generate` 函式。它可以是任何將一個回合轉換成文字串流的回應產生器。 使用 Workflow 時,worker 不會像 Agent 的 `stream()` 一樣自動保存回合。請在 Workflow 內保存對話記錄,或將 LiveKit 轉錄內容作為真實資料來源,並在每個回合傳入。 ## 將 Mastra 作為 LLM 元件 `createLiveKitWorker()` 會替你掌控 LiveKit 工作階段。若要自行掌控工作階段,請改用 [`MastraLLM`](https://mastra.zisheng.pro/zh-TW/reference/voice/livekit):這是標準的 LiveKit LLM plugin,會將 Mastra Agent 放入你自己的 `voice.AgentSession` 的 `llm` slot。Mastra 應用程式、Agent loop、Tool、Memory 與 Observability 會在 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' }) } ``` 兩種方式底層共用相同的回應 pipeline;請依工作階段的擁有者進行選擇: | | `createLiveKitWorker()` | `MastraLLM` | | --------------- | ------------------------------------------------- | --------------------------------------------------------------------------------- | | 工作階段擁有權 | Worker helper 建立及管理 `AgentSession` | 由你的程式碼建立工作階段;你可掌控所有 LiveKit 選項與 hook | | Mastra 應用程式執行位置 | Worker 處理程序中 | Mastra 伺服器上,透過 HTTP 存取(或透過 `agent` 在處理程序內存取) | | Worker 處理程序需要 | Mastra 應用程式、Storage 與模型 Provider 金鑰 | 僅需 LiveKit SDK 及對伺服器的網路存取權 | | 內建便利功能 | 問候語、同意機制、由 Agent 主動掛斷、thread 初始化、Observability 彙總 | 使用[工作階段 helper](https://mastra.zisheng.pro/zh-TW/reference/voice/livekit)自行建置所需功能 | | 最適合 | 以最快方式建立可用的 Voice Agent;Studio 語音模式 | 現有 LiveKit 應用程式,以及需要完整掌控工作階段的情境 | Tool 會保留在 Mastra Agent 上並於伺服器執行。傳入工作階段的 LiveKit 端 Tool 會被忽略。Tool 活動會透過 `toolFeedback`(播放填補語)、`onToolCall`(在每次 Tool 呼叫開始時觸發)與 `onTurnComplete`(在每次回應後觸發,並提供文字、Tool 呼叫及 token 用量)傳送至 worker。只需幾行程式碼即可讓 Agent 主動掛斷:搭配使用 `onToolCall` 與 [`runEndCall()`](https://mastra.zisheng.pro/zh-TW/reference/voice/livekit)。 > **警告:** 請勿同時使用 `memory` 選項與 LiveKit 的 `preemptiveGeneration`;自行建置的工作階段預設會啟用後者。如果推測性回合在 LiveKit 捨棄前執行完畢,就會將一則使用者訊息和一則從未播放的回應保存至 thread。請設定 `turnHandling: { preemptiveGeneration: { enabled: false } }`,或在不使用 `memory` 的情況下執行,並於每個回合傳入完整轉錄內容。 `MastraLLM` 也接受處理程序內的 Mastra `agent` 執行個體,讓你不必進行第二次部署即可掌控工作階段;也可以接受自訂 `generate` 函式。遠端傳輸功能亦可透過 [`createRemoteAgentReplyGenerator()`](https://mastra.zisheng.pro/zh-TW/reference/voice/livekit) 獨立使用,並能接入 `createLiveKitWorker` 的 `generate` 選項,讓功能完整的 worker 搭配遠端伺服器執行。 ## 由伺服器發起的工作階段 使用 `dispatchVoiceSession()` 從自己的程式碼將 Voice 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' }, }) ``` ## Observability Mastra 執行個體設定 [Observability](https://mastra.zisheng.pro/zh-TW/docs/observability/overview) 後,worker 會追蹤每通電話。每個工作階段都會開啟一個 `voice call` span,並將所有項目巢狀置於其中: - 每個回合的 Mastra Agent 執行,包括模型產生、Tool 呼叫與 Memory 操作,記錄方式與文字聊天完全相同。 - 每項 LiveKit pipeline 指標各有一個子 span:語音轉文字、文字轉語音、語句結束(回合偵測)、語音活動偵測,以及模型產生第一個 token 的時間。這些 span 會記錄文字 Trace 無法呈現的延遲與音訊測量資料。 - 各模型的用量彙總(整通電話的 token、字元與音訊總量),並在工作階段結束時寫入 span。 Worker 是獨立處理程序,因此請將 Storage 指向可接受伺服器與 worker 同時寫入的後端。以 SQLite 為基礎的 [LibSQL](https://mastra.zisheng.pro/zh-TW/reference/storage/libsql) 可以正常運作,僅允許單一寫入者的 Storage 則不行。Trace、Memory 與 thread 可以共用同一個 Storage: ```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 伺服器是不同的處理程序,因此 `mastra build` 必須將其輸出為獨立 entry。請將它加入 [`bundler.entries`](https://mastra.zisheng.pro/zh-TW/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 ``` 將該目錄部署為單一 artifact,並使用各自的指令啟動每個處理程序: ```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 dispatch 至 room。Dispatch 會攜帶 Mastra Agent ID、Memory thread 與 resource 等 metadata。 2. LiveKit Agent worker(獨立且長時間執行的處理程序)接收 job 並執行音訊 pipeline。音訊會透過 WebRTC 在瀏覽器與 worker 之間傳輸,絕不會經過 Mastra HTTP 伺服器。 3. 每當使用者結束一個回合,worker 就會以新的輸入呼叫 Mastra Agent 的 `stream()`,並播放串流文字。使用者插話時,LiveKit 會取消串流,Mastra 也會停止產生內容。 對話記錄位於 Mastra Memory,因此語音工作階段與文字聊天可共用同一個 thread。 ## 相關資源 - [`@mastra/livekit` 參考文件](https://mastra.zisheng.pro/zh-TW/reference/voice/livekit) - [語音轉語音](https://mastra.zisheng.pro/zh-TW/guides/voice/speech-to-speech) - [Agent Memory](https://mastra.zisheng.pro/zh-TW/docs/memory/overview) - [LiveKit Agents 文件](https://docs.livekit.io/agents/)