# 你的客服電話,正在被自己的回音罵醒? 很多人第一次接語音模型都會撞上同一堵牆:模型對著喇叭自言自語,字幕同一句話印十幾遍,問它是誰,它還答錯出身。xAI… · @swordBluesy · TwKit
@swordBluesy
# 你的客服電話,正在被自己的回音罵醒? 很多人第一次接語音模型都會撞上同一堵牆:模型對著喇叭自言自語,字幕同一句話印十幾遍,問它是誰,它還答錯出身。xAI 在 2026 年 10 月 14 日發布的 Grok Voice 3.0,就是專門拿來治這三種毛病的即時語音模型,而這篇教程把三個修法一次寫進程式碼裡。 ## 一、它到底是什麼 Grok Voice 3.0 是 xAI 的語音對語音模型:你用一條 WebSocket 把麥克風音訊流過去,模型直接用聲音回答,事件格式與 OpenAI Realtime API 相容。音訊格式固定為 16-bit PCM、24kHz、單聲道、小端序,100 毫秒就是 2400 個採樣點、4800 位元組,算幀的時候這個數字不能錯。 做之前先備齊四樣東西:Node.js 18 以上加 `ws` 8.x,系統裡裝好 SoX(Mac 用 `brew install sox`,Debian/Ubuntu 用 `sudo apt install sox`,程式用它的 `rec` 採麥克風、`play` 放聲音),一個能連線的 API 金鑰,以及一副耳機。耳機不是建議,是必需品:用開放式喇叭,模型會聽見自己的聲音,然後一直回答自己,停不下來。 ## 二、第一步:連線,並把被拒絕的原因印出來 把下面四段程式碼按順序存進同一個 `voice-agent.mjs`。第一段只做一件事:開 WebSocket,握手被拒就把伺服器的理由印出來,不要讓程式靜悄悄死掉。 ```js import { spawn } from "node:child_process"; import WebSocket from "ws"; const URL = "wss://api.apimodels.app/v1/realtime?model=grok-voice-3.0"; const SOX = ["-q", "-t", "raw", "-r", "24000", "-e", "signed-integer", "-b", "16", "-c", "1", "-"]; const ws = new WebSocket(URL, { headers: { Authorization: `Bearer ${process.env.APIMODELS_API_KEY}` }, }); ws.on("unexpected-response", (_req, res) => { let body = ""; res.on("data", (c) => (body += c)); res.on("end", () => { console.error(`handshake rejected: ${res.statusCode} ${body}`); process.exit(1); }); }); ``` 對照表先背下來:401 是金鑰錯或沒給,400 是模型名錯,402 是餘額不夠一分鐘,429 是同帳號超過 5 個並行會話。金鑰錯的時候不會扣費,這一點可以放心試。 ## 三、第二步:開場就把身份寫死 Socket 一打開,立刻送 `session.update`。聲音、指令、輪次偵測都直接放在 `session` 底下: ```js ws.on("open", () => { ws.send(JSON.stringify({ type: "session.update", session: { voice: "eve", instructions: "You are Grok Voice 3.0, a realtime voice assistant built by xAI, " + "answering for the Acme help desk. Keep answers under three sentences.", turn_detection: { type: "server_vad" }, audio: { input: { format: { type: "audio/pcm", rate: 24000 } }, output: { format: { type: "audio/pcm", rate: 24000 } }, }, tools: [{ type: "function", name: "get_order_status", description: "Look up an order by its id", parameters: { type: "object", properties: { order_id: { type: "string" } }, required: ["order_id"], }, }], }, })); }); ``` 這裡藏著第二個修法。`instructions` 的第一句就是身份:自訂指令會蓋掉模型預設人設,你不寫身份,來電者問「你是誰」時模型就可能胡說出身。你的產品叫什麼名字,就寫什麼名字,不要留空。`turn_detection` 用 `server_vad`,代表由伺服器判斷你什麼時候講完話,你不用自己送結束信號。聲音內建 28 種,每種都講所有支援的語言。 ## 四、第三步:麥克風切成 100 毫秒一幀 等伺服器回 `session.updated` 再開麥克風,否則你開頭幾句話會用預設設定處理。SoX 的 `rec` 輸出原始 PCM,程式把它切成 4800 位元組一幀,絕不讓一個 16-bit 採樣被拆到兩個事件裡: ```js let mic = null; function startMic() { mic = spawn("rec", SOX, { stdio: ["ignore", "pipe", "ignore"] }); let pending = Buffer.alloc(0); mic.stdout.on("data", (chunk) => { pending = Buffer.concat([pending, chunk]); while (pending.length >= 4800) { ws.send(JSON.stringify({ type: "input_audio_buffer.append", audio: pending.subarray(0, 4800).toString("base64"), })); pending = pending.subarray(4800); } }); } ``` ## 五、第四步:一個訊息處理器管全部 回音、字幕、工具呼叫,都在這一個 `message` 處理器裡解決: - `input_audio_buffer.speech_started`:來電者打斷模型講話,立刻殺掉目前的喇叭進程、重開一個,排隊的舊音訊直接丟棄,這就是打斷功能。 - `conversation.item.input_audio_transcription.updated`:這是字幕印十幾遍的元兇。xAI 的輸入轉錄是累積式的,每個事件都帶著目前為止的整句話,還可能修正前面的詞。正確做法是覆蓋同一行,絕對不要累加。 - `response.output_audio.delta`:把 base64 音訊解開餵給喇叭;`response.output_audio_transcript.delta`:把模型正在講的文字逐字印出。 - `response.function_call_arguments.done`:解析參數、查自己的資料、把結果用 `conversation.item.create` 送回,再送 `response.create` 請模型把結果講出來。範例裡查訂單編號回「已出貨」,你換成自己的查詢函數即可。 - `response.done`:一輪結束,換行。 最後一件事最容易被忽略:掛斷要關 Socket。會話按連線時間計費,閒置要 10 分鐘完全沒有客戶端事件才算閒置,一直送麥克風音訊(哪怕是靜音)就永遠不閒置,最多跑到 60 分鐘上限。`Ctrl+C` 時殺掉麥克風、喇叭、關掉 Socket,這一步就是在關水龍頭。 ## 六、瀏覽器版:金鑰絕對不能進前端 瀏覽器不能在 WebSocket 上自訂請求頭,所以金鑰絕對不要寫進前端程式碼。正確做法是伺服器端用金鑰換一個短效令牌(有效期 10 到 600 秒,預設 60 秒),前端拿令牌當子協議連線: ```js const { value } = await (await fetch("/api/realtime-token", { method: "POST" })).json(); const ws = new WebSocket( "wss://api.apimodels.app/v1/realtime?model=grok-voice-3.0", ["xai-client-secret." + value], ); ``` 令牌只管連線能不能建立,建立好的會話過期後仍會繼續跑。瀏覽器錄音要自己用 AudioWorklet 採集並重採樣到 24kHz,Firefox 會拒絕把麥克風接到非預設採樣率的音訊上下文,這個坑原文有特別點名。 ## 七、驗收與價格 戴上耳機跑起來,看到 `listening...`,你講的話即時出現在 `you:` 那一行且原地更新。問「你是誰,A-1001 號訂單在哪」,回答應該先報 Grok Voice 3.0、xAI 出品,再用說的告訴你訂單已出貨。按 `Ctrl+C`,會話會在控制台留下一筆通話紀錄,含時長與費用。 價格方面,原文走的閘道是每分鐘 0.104 美元、按秒計費(90 秒就是 0.156 美元),文字訊息每條 0.0052 美元,工具呼叫不另收費。原文也老實提醒:如果你只用 xAI、已有直連權限、又想要最低單價,那就直接連 xAI,不要繞閘道;需要超過 60 分鐘的會話、超過 5 個並行會話、或要在語音會話裡用 xAI 伺服器端工具(網頁搜尋、X 搜尋、檔案搜尋、MCP)的,也不要走這條中繼。 ## S1哥觀點 語音 Agent 的門檻從來不是模型會不會說話,而是三個工程細節:回音、字幕、身份。一個連自己名字都講錯的客服,再流利都是笑話;一個會被自己回音罵醒的客服,連笑話都算不上。Grok Voice 3.0 真正值錢的不是那條 WebSocket,而是它逼你把身份寫進第一句指令。問題留給你:你的語音助手第一次開口,會先報自己的名字,還是先等使用者問你是誰? 原文連結:https://dev.to/super_lewis/build-a-grok-voice-30-voice-agent-in-nodejs-mic-in-speaker-out-live-transcripts-5al6 原文:@super_lewis(由 S1哥 編譯)
曝光 325 · 评论 4 · 点赞 5 · 书签 0
查看原帖