
跟昨天一樣,我們讓 claude 繼續把工作做完,同時產生紀錄:
客戶端也要接上
昨天的結尾留了一句:讓客戶自己用講的留言進來。今天做的就是這句話的後半段——昨天做完的是員工自己在系統裡用講的,今天要接上的是客戶端。客戶對聊天機器人說「留言……」,說的話會被轉成文字,ERPNext 自動找到或建立一筆潛在客戶,留言原文掛在那筆資料的時間軸上,接著系統回一句文字、再回一段語音,聲線是系統設定裡選的那個。示範用的是自己的帳號。
規則:一句預告拆成兩天做,昨天做員工端,今天輪到客戶端。
零件都已經在跑
接客戶留言要用到的東西一樣都不缺。這支聊天機器人本來就會把語音轉成文字,回覆語音推播給對方那條路昨天也已經做好,今天不是重新裝一套,是把幾個現成的零件接起來。客戶講的話不會進任何一個大語言模型,只會經過語音辨識引擎;要不要走這條新路,是一段確定性的字串比對在判斷,跟模型無關。紀錄裡存的話本身沒有被機器消化過,是人自己看的原文。
規則:先盤點手上已經有什麼在跑,新功能常常是把既有的東西接起來,不必每次都重蓋一台。
留言兩個字是開關
客戶講的話或打的字,只要開頭是「留言」兩個字,就會被攔下來走進 ERP 那條新路,其他所有語音跟文字都照原本的規矩跑,不會受影響。這支聊天機器人本來聽到語音會直接當一般對話處理,現在多了一個確定性的切換點:命中「留言」開頭,就不進原本的對話邏輯,也不進既有的另一種前綴指令;沒命中,一切照舊。
規則:要在既有系統裡開一條新路,先找一個明確、不會誤判的切換點,不要讓新舊兩條路混在一起判斷。
1. 程式在 LINE 那台,用既有的免密金鑰連進去;改檔前先備份成 <檔>.bak-20260917;
不 commit、不 rebuild、不 restart 任何容器(部署另外排時段做);測試一律用一次性容器跑。
2. 不 cat 任何 .env、不印憑證值;要知道有哪些設定名稱,只列鍵名,不印值。
3. 只動自己負責的那個專案;既有的訊息分派邏輯一行都不改,只能用插入的方式加進去。
4. HTTP 失敗一律 fail-soft,回一句固定文案,不能讓對話介面收到伺服器錯誤(訊息平台會重送)。
5. 偏離規格可以,但要標明理由;回報固定四段:改了哪些檔(含雜湊)/測試輸出原文/
實測數字/沒做或存疑。
轉錄直接借道
客戶的語音留言,轉文字這一步改成先送去昨天做好的那條辨識路徑,等於免費拿到昨天補好的三個判準:沒聽到會回沒聽到、純靜音底下編出來的話會被擋下來、簡體殘留字會被轉回繁體。這條路如果掛掉或狀態異常,會退回原本直接打辨識引擎的那條舊路,不讓這支聊天機器人的所有語音功能跟著一起啞掉;退回舊路之後留言照樣進得了系統,只是少了那三個判準,語音回覆也可能出不來,文字回覆不受影響。
規則:新功能接上昨天做好的那條路,順便把昨天補的防線一起繼承過來;但退路要留著,一條路掛了不能拖垮其他本來好好的功能。
認人靠一個編號
客戶對應到 ERPNext 裡哪一筆潛在客戶,靠的是這支聊天機器人裡客戶的使用者編號,同一個人不管留言幾次,都掛在同一筆資料上。第一次留言的時候,系統裡找不到對應的潛在客戶,就自動建立一筆,名字直接用客戶在聊天工具裡設定的顯示名稱,不用另外輸入。
規則:客戶端的資料要能自動長出來,不必等業務手動開一筆,才留得住第一次接觸的紀錄。
潛在客戶表單上的「LINE 使用者 ID」欄位(ID 已遮)

原文原封不動上時間軸
不管是語音留言還是文字留言,原文都會直接掛到那筆潛在客戶資料的時間軸上,前面加一個小圖示分辨是語音留言還是文字留言,內容不加工、不摘要,一字不改地存進去,業務自己看一遍就知道客戶說了什麼。示範送出的那句語音留言裡,「型錄」又一次被聽成「行路」——同一個同音字問題,這是第三次真的發生,先前在直接餵錄音檔跟用瀏覽器錄音兩次測試裡各中過一次。這次沒有像昨天那樣先跳出一個可以編輯的預覽框,聽錯的字就原封不動存進時間軸,因為這條路要留的是客戶原本講的話,不是一份經過整理的正式記錄。
規則:客戶自己講的話,存進系統的時候不要被任何一層加工過,留原文才是真的證據,就算聽錯字也一樣。
潛在客戶表單的時間軸上,兩則留言原文都掛上去了

先回文字再回聲音
客戶留言之後,系統先回一句固定的文字,告訴對方已經收到、業務會盡快回覆;接著再合成一段語音推給留言的客戶本人——走的是昨天那條合成路徑,聲線就是 ERP 系統設定裡選的那一個,不是今天另外挑的。文字先回,語音隨後才到。
規則:客戶聽到的聲音,跟員工在系統裡聽到的答案,用的是同一個設定,不用為了客戶端另外再選一次。
第一輪全部打回票
部署完第一輪拿真的帳號測,兩則留言送出去,回來的都是同一句「系統暫時無法使用」。查下去發現,負責留言這件事的那個帳號,對留言這個動作本身只有讀取權限,沒有建立權限,寫入被系統擋下來,回應碼是拒絕存取;但潛在客戶那筆資料其實已經建立成功,因為找/建那步在留言之前。改法是不要再打那支對外開放建立留言的介面,換成後台介面自己內部用的那個加留言的功能,只要對那筆資料有讀取權限就能用,跟昨天在瀏覽器裡把語音備註存進去用的是同一條路。副作用是第二輪重新示範的時候,沒再看到「已經幫您建立聯絡資料」那句提示,因為那筆潛在客戶在第一輪失敗的時候就已經建好了,要重看那句提示得先把那筆資料刪掉。
規則:一個帳號能讀不代表能寫,權限要照實際的動作分別給;資料建立跟留言掛上去是兩個獨立的步驟,一步失敗不代表前一步也沒成功。
症狀:留言介面回應拒絕存取,兩則測試留言都被擋下;潛在客戶那筆資料其實已經建立成功。
原因:這個介面目前用的帳號,對留言這個動作只有讀取權限,沒有建立權限。
修法:改走後台介面本身內部用來加留言的那個函式,不要再打對外那支留言介面;
那個函式只要對該筆資料有讀取權限就能用。
要求:修完要能重現「留言真的掛上時間軸」,並補一支測試涵蓋這個權限組合,不能只靠人工點一次算過。
設定加了卻沒被讀到
負責合成語音回覆的那個服務,往設定檔裡加了一個新位址,照理說重新啟動之後就會吃到,結果重啟完,服務還是回報那個設定沒有值。查下去發現,同一台機器上另一個負責接聊天機器人的服務,設定檔用的是整份載入的方式,改完直接生效;但負責合成語音的這個服務,設定是在啟動時一項一項對應進去的,設定檔裡新加的那一行沒有被對應到,等於白加。改法是把新設定直接加進啟動時逐項對應的那份清單裡,再重新啟動一次才真的吃到。
規則:同一台機器上的服務未必用同一種方式讀設定,加了一行進設定檔不代表所有服務都看得到,要對照那個服務實際讀設定的方式。
四則測試兩收兩擋

兩邊各自補了自動測試:負責接線的那支程式原本沒有測試,這次補到 17 支全部過;負責語音回覆那邊的測試從 17 支加到 23 支,原本就存在、跟這次無關的那一支失敗維持不動、沒有動它。接線那段程式改動:2 行取代、44 行新增。轉錄花的時間:一句語音留言 0.61 秒(12 個字)、只說「留言」兩個字 0.22 秒、一段純靜音 1.14 秒被判定沒聽到。回覆合成的語音各花了 3.18 秒與 3.16 秒。

四則實測:第一則講一句完整的語音留言,時間軸上出現一筆語音留言、收到文字回覆、也收到語音回覆;第二則打一句完整的文字留言,結果一樣;第三則語音只說「留言」兩個字、後面沒接內容,判定不算開關命中,照舊當一般語音處理,沒有進 ERP;第四則是一段純靜音,回覆是請再說一次,一樣沒有進 ERP。潛在客戶那筆資料的時間軸上,兩則留言各自有各自的時間戳記,先前後順序看得出來哪一則先到。語音回覆確認收到,聲線也確認是系統設定裡選的那一個。

規則:四種情況都要真的送一次,成功的兩則跟該擋下來的兩則各自要看得到對應的結果,不能只測成功的那兩則。

到這裡,客戶自己對著聊天機器人講一句「留言……」,話就會原封不動掛進系統裡,業務看得到、也會收到文字加語音的回覆,員工端跟客戶端兩層都接上了。
明天,我們做個總結。
本文為 iThome 2026 鐵人賽「一個人的機房」系列第 29 篇,同步發表於 iT 邦幫忙。