把限制寫成程式,而不是寫進提示詞
要模型「每句七個字、句尾押韻」,它會回你一段看起來很像的東西。我們改成在程式裡數:字數逐句比對,韻腳按十三轍歸類,沒過的那幾句退回去重寫。這篇講這個迴圈怎麼做,以及路上撞到的一個 pinyin 錯誤。
寫歌詞的人不會說「請盡量押韻」。他手上有一段旋律,旋律把每一句的長度直接交給他——這句六個字,下句九個字,副歌那句只有四個字。這不是風格偏好,是硬限制。
語言模型很擅長寫出看起來符合限制的東西。你要它每句七個字,它給你六個字、七個字、八個字混在一起的一段,讀起來還挺順的。你要它押韻,它給你「光」和「亮」——這兩個字在國語裡不押韻,江陽轍和遙條轍差得遠。
問題不在模型不夠強。問題在於,沒有人去數。
先數,再談
我們在 詞光 LyricLab 裡把這件事拆成三步,中間那步是重點:
- 模型照著主題和句長寫一次。
- 程式逐句量:字數對不對、韻腳落在哪個韻轍。
- 沒過的句子——只有沒過的那幾句——連同「你差在哪」退回模型重寫。
第二步完全沒有模型參與。字數是 length,韻腳是查表。這代表出來的結果不是「模型宣稱它遵守了」,而是「我們數過了」。首頁那個 demo 顯示的狀態列也不是動畫效果,它報的是這個迴圈真的走到哪一步。
模型負責想句子,程式負責說它不算數。
押韻不是比對尾字
最容易做錯的是韻腳。直覺做法是比對最後一個字的拼音字尾,但那會出錯得很難看。
國語的傳統做法是十三轍——十三個韻族,一個寫詞的人腦子裡本來就有這張表。同一轍就押,不同轍就不押,跟尾字長得像不像無關:
| 情況 | 判定 | 為什麼 |
|---|---|---|
| 昏 (hun) / 群 (qun) | 押 | 都在人辰轍 |
| 昏 (hun) / 孤 (gu) | 不押 | 人辰 對 姑蘇 |
| 光 (guang) / 亮 (liang) | 不押 | 江陽 對 遙條 |
所以 lib/rhyme.ts 做的是:取句尾那個漢字,用 pinyin-pro 拿到它的韻母,再把韻母映到十三轍其中一轍。聲調直接忽略,因為唱起來本來就不分。
撞到的那個 bug
pinyin-pro 在 n、l、j、q、x 後面會正確回報 ü。在 y 後面不會。
「魚」回來是 u,「月」回來是 ue。照單全收的話,「魚」會被歸進姑蘇轍,然後跟「孤」判定為押韻——這是錯的,而且錯得很安靜:沒有例外、沒有警告,只有一首歌裡某幾句莫名其妙地被放行。
這類錯誤是「在程式裡驗證」這個做法本身的成本。你把判斷從模型手上拿回來,就得為判斷的正確性負責。差別在於:程式錯了,你可以寫一個測試把它釘住;模型錯了,你只能再拜託它一次。
鄰韻是設定,不是含糊
實務上完全嚴格的同轍限制太緊,寫起來會卡。所以我們加了鄰韻:允許幾組聽感相近的韻轍互押。
重點是它是一個明確的開關,不是「差不多就好」。使用者選了鄰韻,系統就按鄰韻的規則檢查;沒選,就按同轍檢查。兩種情況下都是數出來的,只是尺不同。寬鬆可以是一個設定,但不能是一句謊。
字數容差也一樣:容差 ±1 是使用者選的,選了之後八個字的句子在七字限制下會過,九個字不會。
這樣做值得嗎
多一次往返、多一次計算、多一份要維護的韻表。換來的是:當畫面上寫著「每句七個字」的時候,那七個字是被數過的。
我們對其他產品也是同一條規矩。能量的就量,量不了的就不要寫在頁面上。