我花一天寫中文 TTS 規則表,下午一句 prompt 全刪了
AI 落地實戰·8 分鐘

我花一天寫中文 TTS 規則表,下午一句 prompt 全刪了

AI 把『垃圾』念成中國腔的 lā jī,我做了 37 條同音字替換表想修正,下午突然想試一句 prompt — 結果 prompt 完勝。中文 TTS 從規則時代走到 LLM 時代的真實開發紀錄。

Y
Young Tsai

你有過那種「做完才發現根本不用做」的經驗嗎

我這天是 — 早上認真寫了 37 條中文發音規則,下午全變 dead code

如果你是工程師,這篇講的是 LLM 把舊規則式系統翻盤的真實案例 如果你是做台灣中文產品的 PM 或創辦人,這篇告訴你:你的工程師現在可能省得掉一半 TTS 維護成本,音質還更好

事情怎麼開始的

我手上有一個中文閱讀產品,學生會聽 AI 念課文學讀音

有天 PM 傳訊息來:「垃圾被念成 lā jī 了,這是中國發音,我們要台灣發音 lè sè」

中文教學產品 AI 念錯音 = 教育事故,這不是能「再觀察」的小 bug

我打開 TTS 試聽一次,確實是中國腔,故事就這樣開始


PM 的四個要求

做之前先釐清需求,PM 的清單很乾脆:

  • 台灣腔(攻擊念 gōng jí,不是 gōng jī)
  • 自然不機械
  • 多音字要對(喝采的喝念 hè 不是 hē)
  • 人名地名要對(戴資穎、陳彥博、214 周、2021 年)

市面上能用的 TTS 不止三家,但我們先試這三個:

Provider台灣腔成本品質
Azure Speech(zh-TW HsiaoChen)✅ 原生貴好,略僵
Google Chirp3-HD❌ 中國腔中非常好
Gemini Flash TTS(preview)🟡 可 prompt 指示便宜好

這三家都試過,每家都有自己的遺憾


第一版:Azure + SSML <phoneme>

最早的生產版本是 Azure,搭 SSML 的 <phoneme> 標籤手工修多音字:

<phoneme alphabet="x-microsoft-zhuyin" ph="ㄏㄜˋ">喝</phoneme>采

你看這段 XML 應該就懂了,每個多音字都要手編一次注音標記

Azure 的好處是台灣腔原生正確,壞處是

  1. 貴,bill 每月一直累積
  2. 每個新多音字都要工程手動加規則
  3. 音質略僵,不像真人

這個模式跑了一段時間,我一直在想有沒有更便宜更自然的做法


第二版:Gemini + 37 條同音字替換表

Google 推出 Gemini Flash TTS preview 的時候我眼睛一亮

成本超低(兩千多句音檔跑完只要 $0.30 美金),音質又好,就是

它預設念中國腔

那怎麼辦?我就想出一個我現在回頭看覺得很奇葩的方法

TTS 只看發音不看字義,那我把字換成同音的台灣字不就好了?

舉「攻擊」這個例子:

  • 大陸腔:擊 念 jī(一聲)→ 整個詞變 gōng jī
  • 台灣腔:擊 念 jí(二聲)→ 整個詞應該是 gōng jí
  • 我的解法:找一個台灣腔念 jí 的同音字 → 「急」
  • 把「攻擊」改寫成「攻急」送進 TTS,賭它照著注音念,就會念出正確的 gōng jí

而且這不是個別案例,台灣 vs 大陸發音不同的「同字異音」詞一抓一大票:

詞台灣腔大陸腔差在哪
垃圾lè sèlā jī整個詞兩字都不同
攻擊gōng jígōng jī擊:二聲 vs 一聲
企業qì yèqǐ yè企:四聲 vs 三聲
研究yán jiùyán jiū究:四聲 vs 一聲
危險wéi xiǎnwēi xiǎn危:二聲 vs 一聲
微笑wéi xiàowēi xiào微:二聲 vs 一聲
期待qí dàiqī dài期:二聲 vs 一聲
質量zhí liàngzhì liàng質:二聲 vs 四聲
法國fà guófǎ guó法:四聲 vs 三聲
喝采hè cǎihē cǎi喝:四聲(喊)vs 一聲(飲)
認識rèn shìrèn shi識:四聲 vs 輕聲

繁體字面一模一樣,發音卻差一截,這就是中文 TTS 在台灣產品上的長期痛點

這份清單其實官方就有,可以逐字查到完整的台灣讀音:

問題是 — 我們知道清單,但改不動 model

Azure 那種規則式 TTS 走不出僵硬感;Google 高品質的 Chirp3-HD 又是中國腔不能改;自己 fine-tune 一個台灣腔模型?預算和資料都不夠

剩下的路只有兩條:

  1. 改字 workaround:用同音字騙 TTS 念對 — 這就是 B 版的實驗
  2. 改 prompt:找一個聽得懂自然語言指令的 model — 後來才發現 Gemini 3.1 preview 就是

這篇文章接下來就是講這兩條路怎麼走,以及為什麼 #1 看起來聰明、做完才發現會破壞別的東西

這個改字策略背後有個我當時沒檢查的假設:TTS 把字當音標念,不在乎是不是真的詞

_TAIWAN_TTS_REPLACEMENTS = [
    ("垃圾", "樂色"),     # TW lè sè vs CN lā jī
    ("研究", "研舊"),     # 究 TW jiù vs CN jiū
    ("危險", "圍險"),     # 危 TW wéi vs CN wēi
    ("攻擊", "攻急"),     # 擊 TW jí vs CN jī — PM 指名要修
    # ... 37 條規則
]

每一條都是同樣套路:找台灣腔同音的另一個字硬塞,把 TTS 當音標機器用

我還順便做了阿拉伯數字轉中文:214 → 兩百一十四,免得 Gemini 用英文念

做完、跑完 batch、重生 2417 句音檔、上 staging 給 PM 驗收

然後就踩坑了


踩坑:一個沒 call 的 function

PM 聽完回:「攻擊還是中國音」

啥

我打開 log 看,原來是程式漏寫了一段——批次跑的時候,只做了「把標點符號去掉」這一步,沒有套我那 37 條換字規則

送進 Gemini 的還是原文,等於我那張表全部白工,整批音檔白跑一次

修一行 code、重跑 batch、$0.30 又丟出去,總算對了

光這一輪就半天了


為了「相信 AI 念對了」,我做了一套稽核系統

PM 這時候又提了一個我後來覺得超有洞察的要求:

「我要能驗收每一句音檔」

他的擔心很合理 — 規則可能套錯、Gemini 可能念錯、我可能 push 了一個沒檢查的版本

於是我做了:

  1. Append-only JSONL log(29 個欄位/筆):記下每一句送進 Gemini 的原文、替換後字串、哪些規則觸發、音檔 SHA、儲存路徑、生成時間、誰生的、取代了哪個舊版本
  2. 後台稽核頁:PM 可以用瀏覽器翻兩千多句、filter「有套替換」「有數字」「台灣特有詞」、點進去看完整 metadata、直接播音檔抽聽

後來我才發現,這套東西最大的價值不是除錯,是信任

工程師 debug 用 log,其實可以寫粗一點;但產品端要驗收 AI 生成的東西時,需要的是一種「責任可追」的安全感

這一層我之前都低估了


第三版:一句 prompt 就夠了

中午我坐下來,盯著那 37 條替換表看了一下

剛好同個產業的另一個工程師寫了篇 blog(evanlin.com 那篇 Gemini 3.1 TTS 實戰),提到他用 prompt 直接指定台灣腔台灣用語,效果不錯

我自己也再聽了一次原版,發現一件事 —

Gemini 預設那個版本,其實沒有純大陸腔,是有點台灣親切感的混合腔

只是某些字(垃圾、攻擊、研究)會偏中國發音,整體聽感是「大致對、局部錯」

那如果只告訴它「請用台灣腔念」,會不會把那些局部錯的也修掉?

我不知道答案,但 batch 只要 20 分鐘 + $0.30,試一次的成本低到不試白不試

於是我建了個 variant 系統:

  • Variant A:不替換,只加 prompt prefix
  • Variant B:做替換(原本的版本)
prompt prefix = "請使用台灣用語的繁體中文,以親切且自然的語氣朗讀以下內容:"

跑完 A 版 batch,2417 句 / 約 20 分鐘 / 約 $0.30


A/B 盲聽:44 個樣本、6 個類別、PM 拍板

要讓「感覺比較好」變成可驗收的結論,我做了一個 A/B 盲聽網頁

44 個樣本,涵蓋:

  • 台灣發音-高頻(垃圾 / 研究 / 危險 / 攻擊 / 企業 / 成績)
  • 台灣發音-其他(微笑 / 盡量 / 休息 / 質量 / 認識 / 知識)
  • 人名-難字(陳彥博 / 楊俊瀚 / 戴資穎 / 陳雨菲)
  • 地名(東京 / 台灣)
  • 數字(214 / 2021 / 100)
  • 中英混讀(NASA / Hemsworth / PEACE / Frankenstein)

左邊綠色是 A(純 prompt),右邊橘色是 B(替換表),每筆並排放 audio element

這時候要交代一下我這邊的人 — 這個 A/B 盲聽不是我一個人聽

我團隊有兩個高中生實習工程師。兩個都已經能獨立寫 React、跑 git、開 PR review 了,coding 平心而論不輸大學畢業剛入行的職業工程師。也很願意把 AI 當輔助,不會被「匠人手刻 code」這種傳統 coding 認知綁住

而且他們做這個專案還意外地適合 — 高中生剛離開大量看抖音、聽 YouTube 的年紀,對大陸腔跟台灣腔的差異敏感度比我高。我聽「攻擊」的擊念一聲二聲要重複播兩三次才確定,他們一遍就聽出來。我自己長期混在科技業同溫層,腔調聽覺其實鈍化了

prompt 怎麼下也是他們陪我磨的 — 「親切的語氣」「自然朗讀」這些字眼換來換去,A 版最後定稿的那句 prompt 有他們的功勞

所以盲聽我們三個人 + PM 一起聽,結果很一致:

  • A 更自然:整句連貫、語氣親切
  • B 略僵硬:替換字會擾動語意節奏,斷句怪怪的
  • 難字:A 靠 prompt 竟然也能念對陳彥博、楊俊瀚這種不常見人名
  • 數字:A 直接讓 Gemini 自己把 214 周 念成「兩百一十四周」,我的數字轉中文規則根本可以刪

拍板:以後就用 A


一個讓我想了很久的細節:「攻急」為什麼聽起來怪?

其中一個高中生實習工程師盲聽完跑來說:「B 版的『攻急』聽起來很不舒服,不知道為什麼」

我研究半天才搞懂:

回想我們做這個替換的初衷 — 因為大陸腔把「攻擊」的擊念成一聲 jī,我們找了台灣腔同音、念二聲 jí 的「急」來替換,想用同音字騙 TTS 念對

這個策略背後的假設是:TTS 只看注音不看詞義

但 Gemini 不吃這套

「攻急」不是一個真的詞

舊時代的 TTS(Azure 那種)大致是 text → 音素 → 聲學特徵 → 波形 的 pipeline(Tacotron / FastSpeech 這類,Microsoft neural TTS survey 講得很清楚),所以「攻急」跟「攻擊」對它來說只是兩串注音的差別 — 同音替換對它有效

但新一代 LLM-based TTS(VALL-E、NaturalSpeech 3、Gemini TTS)把 TTS 重新定義成「語言模型任務」 — Google 官方 blog 自己就說 Gemini TTS「不只知道要講什麼,也知道怎麼講,會根據 transcript 決定該怎麼說出來」

所以語意脈絡會影響輸出語調這件事,是有官方和論文支撐的,不是我亂講

至於「假詞會讓 LLM TTS 整句韻律壞掉」這個更具體的 claim — 是我從架構推論的合理猜測,但目前我沒看到直接 benchmark 的論文。歡迎懂的人打臉

不過實務觀察是明確的:「攻急」餵進 Gemini,韻律就是會怪,字念對了氣卻不順。如果這個猜測對,那意義就是 — 我們為了修發音,反而干擾了它本來就在做的事

舊 TTS(Tacotron / FastSpeech 類)LLM TTS(VALL-E / Gemini 類)
字符串 → 音素 → 聲學特徵 → 波形字符串 → 看上下文 → 韻律 → 波形
OOV 字常用 G2P fallback 念錯假詞不會崩,但語境韻律會偏
規則 / 字典越完整越好prompt 越精準越好

我們玩家還有再玩一個小細節 — 大陸腔的「摸」會有一點 ㄇㄨㄛ 的捲舌感,台灣腔是純粹的 ㄇㄛ

A 版念「摸」就是台灣的 ㄇㄛ,B 版被替換之後反而失去那個微妙的親切感

這個觀察就是工程師很少談、但用戶會直覺感受到的「口感」


刪掉一天的工作

決策 PR merge 完,我坐在椅子上看著自己早上寫的 code

  • _TAIWAN_TTS_REPLACEMENTS 表 → 變 dead code
  • _apply_taiwan_pronunciation → dead code
  • _numbers_to_chinese_tw → dead code
  • _clean_for_gemini → dead code
  • 為 B 版做的多音字 audit 文件 → PR 直接 close 不 merge
  • 5 個 research issues(多音字 / 中英混讀 / 斷句 / 人名地名)→ 全部關掉,結論都是「A 版 OK」

一天前我覺得這些是產品的核心骨架,一天後它們全變成「保留作 rollback 用」的骨董

但我沒刪掉,因為刪除的成本比保留高很多 — 如果未來 A 版被投訴,revert 一個 PR + 換存儲路徑就回 B


三個我會記到下一次的教訓

一、Prompt 先試,規則後做

37 條規則我做了半天,prompt 寫了十分鐘

說實話這個順序我是做完才懂的,不是一開始就知道 — 我那天的習慣動作是「規則優先,prompt 是 fallback」,老工程師慣性

現在我會反過來:先試 prompt,不夠再做規則

LLM 把 cost-of-experiment 壓得超低,這件事本身就值得改變開發順序

二、Audit trail 的價值是「信任」不是「除錯」

我原本以為 provenance log 是為了自己 debug,做完才發現

它真正的用戶是 PM,他需要的是逐句驗收的權力

工程師被 bug 咬是家常便飯,但產品端對 AI 生成物有一種「我沒親眼確認過的不敢簽」的焦慮,這個心情合理

做 AI 產品,audit trail 其實是讓非工程師睡得著覺的機制,不是 debug tool

三、Variant system 的長期價值

tts-variants.yaml + --variant A|B 這個設計本來是為了一次 A/B 做的,但做完我發現它可以長期存在

未來 Gemini 推出 voice 選項,可以加 Variant C 測試 未來要做低年級專用腔調,可以加 Variant D 儲存分 folder 隔開,cache 不會污染

一次實驗如果設計得好,會自己變成 infra

這是我比較常忽略的一個原則 — 當下花 30 分鐘多寫一層抽象,未來少掉一次架構重構

四、能寫 code 的高中生比你以為的更有用

這篇從頭到尾,AI 念對音的關鍵判斷有不少是兩個高中生實習工程師做出來的 — 腔調差異、prompt 措辭、盲聽結論

我以前對「帶實習生」的想像是:派一些低風險的雜活、幫他們學 git、能跑通就好

實際做下來反過來 — 他們的 coding 能力跟新進職業工程師沒差,年齡反而是優勢:

  • 他們耳朵還沒被科技業同溫層磨鈍,腔調直覺更準
  • 他們對 LLM 的態度比我們這代「自然」 — 不會卡在「規則才是工程、prompt 是作弊」的舊認知
  • 他們會嘗試很多我不會去試的瞎玩法,其中幾個變成最後產品決策

如果你是企業端、又在猶豫要不要找高中生實習 — 我這邊的經驗是:值得。前提是你願意把他們當工程師看,不是把他們當打雜的看


後來幾週發生的事(彩蛋)

刪掉 37 條規則後,省下來的 bandwidth 流到哪去了?

我以為會閒下來,結果反而冒出更多原本被遮住的問題:

一、切句也用 LLM 取代 regex

原本切句用 regex 處理標點符號(句號、驚嘆號、問號、刪節號),維護一串 edge case

既然 prompt 思維在發音上贏了,我就把它複製到切句 — 改用 Opus 4.7 做語意切分

結果跟 TTS 一樣的 pattern:規則時代要維護幾百條 corner case,LLM 直接讀懂語意,輸出 2301 句乾淨的段落

二、Audit trail 進化成「合併閘」

PM 想要逐句驗收的需求,後來變成一個更嚴格的工程紀律:

我寫了一個 --strict 模式的 verifier,每次重生 batch 後,必須驗證 JSONL 條目跟雲端音檔 1:1 對齊(沒多沒少、key 完全 match),否則 PR 不准 merge

從「PM 用來驗收的後台」進化成「工程自己用的合併閘」

三、Safety filter 的優雅 fallback

兩千多句中有兩句被 Gemini 的 safety filter 拒絕生成(不知道為什麼,內容很正常)

解法很漂亮:用同一個 cache key,改叫 Chirp3-HD(中國腔的那家)補生成那兩句

runtime 完全不知道音檔來源,cache 命中就播 — provider abstraction 因為前面 variant system 設計得好,這個 fallback 寫起來只多了 30 行

四、原本看不到的 UX 問題浮上來

發音對了之後,下面幾層問題才看得見:

  • 段落之間音量會跳 → 加了 loudnorm 做 -16 LUFS 響度標準化
  • 字幕高亮跟音檔對不齊 → 改成字元權重的 progress + prefetch
  • TTS 按鈕只有 idle/playing 兩態,loading 跟 error 都讓人疑惑 → 拆成 4-state UX

這些問題一直都在,只是被「念錯腔」的大火蓋住了

滅大火之後,小火才看得到 — 這是工程的常態


成本對比(最後的帳單)

方案人工維護API 月費音質台灣腔
Azure + SSML phoneme高(手編每字)貴好,略僵✅ 原生
Chirp3-HD(中國腔)0中非常好❌
Gemini Variant B(替換表)中(37 條 + 維護)~$0.30/兩千句略僵✅ 靠規則
Gemini Variant A(prompt-only)零~$0.30/兩千句好✅ 靠 prompt

A 全贏,零維護、音質最好、成本最低


我的判斷

至少在這個專案上,我感覺到的是:以前在做的事 — 音素標記、斷句規則、多音字字典、同音替換、SSML tag — 在 LLM TTS 上 ROI 變很低

現在花時間比較有產出的地方變成:prompt 設計、audit trail、variant system、成本監控、信任機制

不是說舊技能沒用,遇到 LLM 韻律不穩定的 case,舊的 phoneme 控制還是 fallback。但「主路」確實位移了

你有沒有也做完一個東西、然後發現它當天就廢了?這種經驗其實是這個時代最值得分享的學習素材


如果你正在做中文教育 / EdTech 產品,遇到 AI 落地的卡點,歡迎直接約 30 分鐘聊聊 — 我這幾年踩過的坑,剛好可能就是你正在踩的

aittschinese-ttsllmprompt-engineeringedtech