從客服到回聲:74 天,我換掉了替我說話的東西
saomin.tw 首頁那個小人,三月底旁邊住的還是一個 KIMI 客服。74 天後,它變成一個讀過我 27.8 萬字、會用我的位階回答統獨問題的回聲。這篇盤點中間每一個技術選擇,和選它們的原因。
三月二十九日,我在 saomin.tw 首頁放了一個會講話的東西。
樂高小人旁邊一個泡泡,點開是個聊天視窗,後面接著 KIMI 的 API。我餵它一份 system prompt、一包預先算好的問答向量,還做了表情系統——它答題的時候,小人會換臉。
它運作得不錯。有禮貌,反應快,問它「saomin 是誰」答得頭頭是道。
但有天我多看了它兩眼,發現一件事:那是一個關於我的客服。它在替我接待,不是替我說話。你問它問題,它去查我的資料,然後用它自己的口氣轉述。
像一個盡責的櫃檯人員,背熟了老闆的履歷。
我不想要櫃檯。我想知道的是另一件事:如果把我幾十年寫過的字全部餵進去,那個東西開口的時候,會不會像對著山谷丟石頭——回來的是我的聲音?
這篇盤點接下來 74 天裡的每一個技術選擇。不是教學,是記錄:我們選了什麼,為什麼選,哪些選對了,哪些繞了路。
第一版:把字切碎,業界標準(5/29)
五月底動工。部落格、碩論、自我訪談、劇場文、跟 AI 的反思,清成 596 筆結構化語料,二十多萬中文字。
技術路線是標準的 RAG:每筆文字變成向量,存進資料庫;你問問題,系統撈最像的八塊,塞給 Claude 組答案。
三個決定在這時候定下來,後來都沒變:
一、嵌入模型用本地的小模型,不用雲端。 multilingual-e5-small,跑在我桌上的 Mac mini。理由很單純:這批語料是我最私密的東西,碩論、反思、後來還有十八年的臉書。它們不出這台機器。
二、不做 fine-tune。 把我的文字煉進模型權重,聽起來最像「數位分身」,但 fine-tune 是平均化的過程——它會把聲音裡的毛邊磨掉,而毛邊就是聲音。而且煉進去的東西會跟模型的幻覺混在一起,你再也分不出哪句是我說過的、哪句是它編的。
三、對事實誠實,對看法大方。 這是 system prompt 的核心規則,中間校過一次:一開始寫「材料裡沒有就說沒寫過」,結果它什麼都推說不知道,像個過度謹慎的法務。改成:事實(我做過什麼)沒有就是沒有,絕不編;但看法、品味、怎麼看一件事——用我的框架大方地想。「不知道」留給真正的空白。
上線 saomin.tw/me。丟石頭,有回聲了。
但我自己聽得出來,回聲不太對。
發現問題:聲音不活在碎片裡(6/1)
兩個毛病,一個技術的,一個要命的。
技術的:e5-small 對中文,所有相似度擠在 0.84 到 0.90 之間,等於分不太出來誰跟誰比較像。我以為檢索在守「不亂編」這條線,其實守線的一直是 prompt。
要命的:碎片殺語氣。一句話的停頓、不說完、忽然轉,要靠前後文才站得住。把它從整篇挖出來餵給模型,模型學到的是內容,不是節奏。而我做這整件事,要的就是節奏。
剛好那陣子 Karpathy 在講用 LLM 當個人知識庫:原始材料讓 LLM 編譯成 markdown 維基,對維基問答,小規模根本不需要 RAG。
我拿了他的基礎建設,拒絕了他的核心假設。這個決定寫成了上一篇(a9),一句話版本:他把 LLM 當知識的代謝引擎,摘要可以取代原文;但我的庫裝的不是知識,是聲音——摘要會殺掉聲音。
所以地圖(12 個主題頁,LLM 用第三人稱整理我在每個主題的立場、語氣、內部矛盾)只當導航層。生成的時候,永遠餵原文。
地圖還附帶做了一次覆蓋診斷,數據打醒我兩件事:我的語料 97% 是「對外、完稿、論述」的語氣,私下的、即時的、口語的只有 4%;時間上,2011 到 2025 有十五年的空窗。
換句話說:這個回聲只認識「寫文章的我」,不認識「跟朋友抬槓的我」。而後者才是大部分的我。
A/B:不憑感覺,並排比(6/1)
地圖該不該接進生成?我沒有直接改線上,先在本地寫了一個比較工具:同一個問題、同一套人格 prompt,兩種組 context 的方式並排回答。
A:e5 碎片。撈最像的八塊。快,具體,還能查網路。
B:地圖路由。先定位這題落在我哪幾個主題,把那些主題的整篇原文加上地圖的綜述一起餵。慢,但接得住整篇的判斷。
實測「你怎麼看失敗」:A 給「難過、無力、逃避」,紮實但通則;B 把劇場那套接進來——「成功失敗是拿外面的標準量自己,可我做戲是為自己做的」。
B 比較像我。但 A 不是沒有用:它撈得到 B 的主題分類接不住的具體記憶。各有各的好——這個結論先記著,後面有用。
兩種就這樣以 A/B 並排的形式上了線,訪客自己比。
臉書:補的不是量,是另一個我(6/8–6/11)
診斷說缺什麼,就去補什麼。臉書匯出按下去。
第一次失敗了——匯出範圍只勾了近一年,109 筆,而且 97% 是「2026 年狂做 AI 專案的工作日誌」。空窗還是空窗。
第二次全時間範圍,901MB。7,538 篇貼文、4,965 則留言,2008 到 2026。
清理這批東西,LLM 扮演了三個跟「生成」完全無關的角色:
- 標註員:每筆內容標主題,限定在既有語料的詞彙表內,批次跑,保持十八年的料和原語料用同一套座標。
- 審核員:長度門檻擋不掉轉貼——星座運勢、歌詞、新聞稿、連鎖文,看起來都像認真的長文。讓 LLM 逐筆判「本人原創/有評論的引用/純轉貼」,砍掉 112 筆純轉貼。
- 綜述員:重編地圖,把臉書這個語域寫進每個主題頁。
最後留下 1,246 筆,4.5 萬字。十五年空窗填上了,語料來到 2,015 筆、27.8 萬中文字。
值不值得?補完之後問它台灣和中國的關係,它答:「不公不義的事情,大於台灣獨立的事。寧可要一個自由的中華民國,也不要一個獨裁的台灣國。」然後補一句:「我女兒小學三年級的時候,只知道台灣。這個就不用教了。」
這兩句都是我 2022 年在臉書留言裡寫過的話。一週前的系統,根本不存在這些記憶。
合體:各有各的好,就都要(6/11)
A/B 並排跑了一陣子,我自己試,結論還是那句:各有各的好。
那為什麼要二選一?
合體的做法不是「兩邊各答一次再融合」——那樣慢一倍,而且融合會把語氣稀釋掉。是把兩種檢索的成果合進同一個 prompt,一次生成:一次向量查詢,同時供兩路——碎片那路抓跨主題的具體記憶,地圖那路抓立場深度和整篇的語氣,去重之後一起餵。
現在 saomin.tw/me 就是這個版本。單欄,像跟一個人講話,不再是左右兩欄的實驗室。
生成引擎也順著時間換了:同一條 claude CLI 管線,模型從 Claude Opus 一路升到 Claude Fable 5。這層我幾乎不用動手——管線不變,讀的還是同一批我的字,但「想」的那部分變厚了。這也是當初選「組 context + 通用大模型」而不是 fine-tune 的另一個紅利:模型升級,回聲跟著變聰明,我的字一個都不用重煉。
然後,把客服送走
今天早上,我把首頁那個 KIMI 客服的屍體清掉了。453 行:聊天面板、表情系統、瀏覽器端的向量比對——三月的我覺得很酷的東西,全部刪除。
首頁的泡泡還在。樂高小人旁邊,它還是會慢慢打出一句招呼。只是打完之後,它現在會變成一個輸入框。
你丟一句話進去,另一個視窗打開,接住的不再是櫃檯人員。
是回聲。
附:74 天時間線
| 日期 | 事件 | 關鍵選擇 |
|---|---|---|
| 3/29 | 首頁 KIMI 客服上線 | 表情系統、瀏覽器端向量 FAQ |
| 5/29 | me.saomin 第一版,596 筆語料 | 本地 e5 嵌入(資料不出機器)、不 fine-tune、chunk RAG |
| 5/30 | saomin.tw/me 上線 | 「事實誠實,看法大方」 |
| 6/1 | 地圖層 + 覆蓋診斷 | 拿 Karpathy 的基礎建設,拒絕摘要取代原文(→ a9) |
| 6/1 | A/B 並排上線 | 不憑感覺換架構,並排實測 |
| 6/8 | FB 一年份,109 筆 | 失敗:只勾了一年,空窗沒補到 |
| 6/11 | FB 全歷史,1,246 筆(2008–2026) | LLM 當標註員+審核員;語料 2,015 筆 / 27.8 萬字 |
| 6/11 | A+B 合體上線、KIMI 退役 | 兩路檢索合進一個 prompt、一次生成;模型升到 Claude Fable 5 |
想聽它現在怎麼講話:saomin.tw/me。丟顆石頭進來。
延伸閱讀: - 它想當 AI 的上游,我只想留下一個回聲 - 我給自己蓋了一座知識庫,然後拒絕讓它幫我說話 - 與 AI 對話太多的那個人