昨天先整理了這次 AI Companion 想完成的整體方向。
如果希望 AI 不只是一個文字聊天機器人,而是真的能成為日常生活中的陪伴角色,那麼第一個要解決的問題其實很直接:
AI 要怎麼聽懂使用者說了什麼?
尤其這次的應用情境是高齡智慧健康照護,相較於打字,我希望主要的互動方式可以以「語音」為主。
例如使用者可以直接說:
昨天晚上沒有睡好。
今天膝蓋有點不舒服。
晚餐吃得比較少。
這些原本只是日常對話中的內容,未來都有可能成為 Health Memory 或健康狀態分析的資料來源。
不過在做到這些功能以前,今天先專心解決一件事情:
讓 AI 穩定、快速地把語音轉換成文字。
Speech-to-Text,簡稱 STT,就是將人說話的聲音轉換成文字。
整個 AI Companion 的語音流程,可以先簡化成:
使用者說話
↓
Speech-to-Text
↓
文字
↓
LLM
↓
回覆
STT 可以說是整個語音系統最前面的入口。
如果辨識結果錯誤,後面的 LLM、Memory 或 RAG 即使做得再完整,也可能因為輸入錯誤而產生不正確的結果。
例如使用者說:
我今天有點頭痛
如果 STT 辨識錯誤,後續對話甚至健康資訊擷取都可能受到影響。
所以第一步,就是先把語音辨識做好。
這次我先使用 Whisper 作為 Speech-to-Text 模型,並選擇:
Whisper Large V3 Turbo
Whisper 支援多語言語音辨識,中文的辨識效果也不錯。
相比完整的 Large V3,Turbo 版本在辨識速度與效果之間取得不錯的平衡,對即時語音應用來說比較適合。
另外,目前主要是在 Apple Silicon 的 Mac 上進行開發,因此我使用 MLX 版本來執行模型。
MLX 是針對 Apple Silicon 設計的機器學習框架,可以利用 Mac 的統一記憶體與 GPU 進行推論。
所以第一版流程很單純:
Microphone
↓
Audio
↓
MLX Whisper
↓
Text
先確認從麥克風輸入語音,到最後輸出文字這條流程可以正常運作。
最簡單的 STT 做法就是:
開始錄音
↓
使用者說話
↓
停止錄音
↓
Whisper 辨識
↓
輸出文字
這種方法實作很簡單,而且辨識效果通常也不錯。
但很快就會遇到一個問題:
使用者說話的時候,系統幾乎沒有任何反應。
假設使用者說了 10 秒,就必須先把整段話說完,再等待 Whisper 完成辨識。
對一般錄音轉文字來說可能沒什麼問題,但如果要做的是 AI Companion,互動感就會比較差。
所以接下來我加入了 Rolling Transcription。
Rolling Transcription 的想法是:
不要等整段語音全部說完,而是每隔一段時間,就拿目前收到的音訊進行一次辨識。
例如:
0~2 秒 → 辨識
1~3 秒 → 再辨識
2~4 秒 → 再辨識
這樣使用者在講話的過程中,就可以持續看到文字更新。
例如一開始可能是:
我今天
接著變成:
我今天早上
最後變成:
我今天早上沒有吃早餐
相較於講完整句才看到結果,這樣的互動體驗會自然很多。
目前我的 Rolling Window 大約使用 2 秒左右的音訊來進行辨識更新。
做到這裡時,其實很容易覺得:
文字已經可以即時更新,那這不就是 Streaming ASR 嗎?
但其實兩者不完全一樣。
Rolling Transcription 本質上還是:
取得一段音訊
↓
送進 Whisper
↓
重新推論
↓
更新文字
只是這個流程持續重複,所以看起來像即時辨識。
真正的 Streaming ASR 通常會持續接收音訊,並保留前面語音的狀態,不需要每次重新處理一整段音訊。
因此目前這個版本比較像是:
利用 Rolling Window 做出接近即時辨識的效果。
真正的 Streaming ASR,之後會再另外實作並進行比較。
做到 Rolling STT 之後,下一個問題是:
系統怎麼知道使用者什麼時候開始說話,又什麼時候講完?
如果沒有判斷機制,就只能讓使用者自己按下開始錄音和停止錄音。
所以我加入了 VAD(Voice Activity Detection)。
VAD 的功能很簡單,就是判斷目前的音訊中:
有沒有人正在說話?
加入 VAD 之後,流程就變成:
Microphone
↓
VAD
↓
Speech Start
↓
開始收集語音
↓
Rolling STT
↓
偵測到一段時間沒有說話
↓
Speech End
這樣就可以讓系統自動判斷一句話是否已經結束。
而 Speech Start / Speech End 之後也會繼續用在 Barge-in 與完整 Streaming Voice Pipeline 中。
Rolling Transcription 雖然可以快速顯示文字,但結果不一定穩定。
例如一開始可能辨識成:
我今天晚上
過一下又修正成:
我今天晚餐
最後才變成:
我今天晚餐吃得比較少
這是因為模型在拿到更多上下文後,可能會修正前面的結果。
所以我的設計是:
Rolling STT
→ 負責即時顯示
Speech End
↓
Final STT
→ 使用完整音訊重新辨識
也就是把「即時性」和「最終結果」分開處理。
Rolling STT 讓使用者可以快速看到文字,而 Final STT 則負責產生最後真正送給後續 LLM 的內容。
目前第一版的語音辨識流程變成:
使用者說話
↓
Microphone
↓
VAD
↓
Rolling STT / Partial Result
↓
Speech End
↓
Final STT
↓
完整文字
其中:
Rolling STT → 提供即時回饋
Final STT → 提供較穩定的最終結果
這也會成為後續語音系統的基礎。
目前在 Mac 上使用 MLX Whisper Large V3 Turbo 進行測試。
其中一次測試結果:
音訊長度:4.5 秒
辨識時間:約 1.31 秒
RTF:約 0.29
RTF(Real-Time Factor)的計算方式是:
RTF = 處理時間 / 音訊長度
因此:
1.31 / 4.5 ≈ 0.29
RTF 小於 1,代表模型處理速度比音訊本身的播放速度快。
以目前第一版 Prototype 來說,這個速度已經可以作為後續即時語音互動的基礎。
今天主要遇到的問題是:
即時性和辨識準確度要怎麼平衡?
Rolling Window 太短,模型拿到的上下文比較少,辨識結果可能不穩定;Window 太長,則會增加等待時間。
另外還需要持續調整:
Rolling Window 長度
更新頻率
VAD 的 Speech End 判斷
Final STT 的修正方式
這些參數都會影響之後的 Streaming Voice Pipeline。
今天完成了第一版語音辨識流程:
Microphone
↓
VAD
↓
Rolling Transcription
↓
Speech End
↓
Final STT
↓
Text
未來使用者在日常對話中提到的睡眠、飲食或身體狀況,也會先透過這條 STT Pipeline 轉成文字,再交給後續的 Health Memory 處理。
今天先完成 AI Companion 的第一個能力:
讓 AI 聽懂使用者說了什麼。
下一篇 Day 03,就要讓 AI 開始真正「開口說話」。
Day 03|讓 AI 開始說話:從 Text-to-Speech 到自然語音。