Day 1寫完之後,我就在等Day 2這天 — Voice Agent。
說實話Voice Agent是這次整個系統裡,我自己最緊張的一塊。
因為其他agent寫信、查資料、跑分流,最差就是寫得普通一點、被Tone Coach擋下來、重寫一輪。但Voice Agent是真的會撥電話出去的。撥出去之後對方接起來,聽到的是Sarah的聲音,講的是我寫的話術,做的決定是我設計的flow。
中間任何一個地方錯,對方掛電話之後不會跟你說「你AI怪怪的」 — 他只會記得「這家公司很討厭」。
收款場景在這件事上特別敏感,因為對方本來心情就不好欸對吧?他可能真的有現金流問題、可能跟客戶吵過架、可能正在等客戶撥款。你AI再多踩一個雷,就是把這個關係徹底搞砸的最後一根稻草。
所以Day 2這天我一直在想兩件事:
第一,Voice Agent要是雙向的,不是單向。
第二,它要是雙向的,就要能聽懂人類真的會說什麼。
為什麼單向voice不夠
最初的Voice Agent設計,是「我們撥過去、講完掛斷」 — 像一個語音版的提醒信。
但我寫到Day 55 last-chance call這段的時候,自己讀了一遍劇本,覺得很怪。
劇本裡Sarah會跟客戶說:
「I know things come up. We can work out a payment plan if you need.」 (我知道有時候會有突發狀況。如果你需要,我們可以安排還款計畫。)
但問題是,講完這句話之後接下來呢?
如果客戶在電話那頭說「對啊,下週給你」、「我需要跟老闆討論」、「其實我們現在現金流出問題」 — 單向voice聽不到啊?
那這句「we can work out a payment plan」不就只是嘴上講講,下一秒就掛斷?
這個設計矛盾我在D-027決策那邊就埋下了。設計的本意就是要對話,所以voice一定要做成雙向。Day 2升級的目標就是把它做完。
ElevenLabs Conversational AI — 在Day -1註冊時就埋好的伏筆
我在Day -1(6/11)註冊ElevenLabs帳號的時候,選的就是ElevenAgents — 它的Conversational AI功能。
當時J跟我說「先用free tier 15 min跑dev playground,沒消耗到實際minute,等真的要撥電話再升級$6 Starter」。
我就照做了。
Day 2這天我做的第一件事,是去ElevenLabs platform把整個ConvAI Agent建起來。
選擇的voice是Sarah — voice ID EXAVITQu4vr4xnSDxMaL,這是ElevenLabs自帶的premade voice,free,定位「mature, reassuring, confident female US-EN」。
我聽了幾個voice的試聽,最後選Sarah的理由很簡單:她聽起來像我自己想接的那種電話。
不是太年輕、不是過度甜美、不是冷冰冰的客服機器人聲。是那種…你會在美國中型銀行打電話進去客服中心,可能會接到的某個中年女主管的聲音 — 沉穩、有溫度、但有專業距離。
收款場景就是要這種聲音。
太年輕對方會懷疑你公司專業度,太老派又會覺得有壓迫感,Sarah剛好落在中間…
18個dynamic variable — 怎麼讓Sarah「認識」這個客戶
ConvAI Agent的prompt是寫死的,但每一通電話的客戶都不一樣 — 不同公司名、不同欠款金額、不同逾期天數、不同pattern。
要讓Sarah聽起來像真的在跟某個特定客戶說話,必須在通話前把「這個case的脈絡」塞進去。
ElevenLabs的做法是dynamic variable — 你在prompt裡寫{{customer_first_name}}、{{invoice_amount}}、{{days_past_due}},撥電話之前API call時帶這些變數進去,Sarah就會在對話裡自動講出對應的值。
我們最後堆到18個variable。
包含customer的姓、公司名、未收餘額、逾期天數、pattern_tag(這客戶以前的拖款模式)、recent_excuse(最近一次他講過什麼藉口)…
Sarah講出來的每一句話都會帶這些context。她不會說「Hi this is Sarah calling about your invoice」 — 她會說「Hi, this is Sarah calling for ABC Trading regarding your invoice from March」。
具體到讓對方覺得「等等,這真的不像是spam call」。
4個tool callback — 怎麼讓Sarah「會做事」
Sarah不只是會講話。她要在通話中做四件事:
第一,當客戶講出藉口(cash flow issue / dispute / overseas wire delayed),她要記下來是哪一類 — log_excuse(category)。
第二,當客戶答應一個還款日期,她要記下來那個日期跟金額 — propose_payment_date(date, amount)。
第三,當對方說「我需要跟人類講話」,她要請callback — request_callback_to_human()。
第四,如果情況需要升級(對方很敵意、提到bankruptcy、提到律師、被識別為錯誤的人),她要丟回去給人類處理 — escalate_to_concierge(reason)。
這四個tool callback是Sarah的「手腳」。沒有它們她只是會講話,有了它們她就會在對話中真的做事。
接這四個tool的方式,是在ElevenLabs的agent setting裡設定每一個tool對應的webhook URL。Sarah講話講到某個點覺得該call這個tool了,她會送一個webhook到我們的server,我們的server接收、寫進audit trail、回傳結果,她繼續對話。
整套接完之後我整個人很興奮 — 因為這就是真的「能聽懂、能做事」的voice了。
但興奮歸興奮,沒測試過的東西不敢說它work。
我自己撥給自己
接下來這段是Day 2最有戲的部分…
我跟J說「我要自己撥一通看看」。
Twilio trial有個機制是只能撥verified number,所以我先verify了我自己的台灣手機+886-XXX-XXX-XXXX。撥的時候在韓國,所以這通電話其實會跨國 — 從Twilio美國號碼撥到台灣號碼,由ElevenLabs的Sarah講英文,跟我(裝成ABC Trading的老闆)對話。
電話響的時候我整個人很興奮不知道AI現在打電話的感覺像不像真人了…
接起來,Twilio trial的「press any key to continue」disclaimer先播放(這段免費版帳戶的demo video後製會剪掉),然後 — Sarah的聲音進來了。
「Hi, this is Sarah calling for ABC Trading. I’m reaching out regarding the outstanding invoice…」
聲音真的很自然。沒有那種AI合成感、沒有奇怪的斷句、語氣是專業但有溫度。
我深吸一口氣,開始照劇本演 — 我假裝自己是ABC Trading的老闆,回她:
「I need to ask my boss about this, I can email you back in 2 days.」 (我需要問一下我老闆,2天內email你。)
這是一個很真實的回應對吧? — B2B場景裡,會接電話的常常不是最終決策者,需要回去討論、等審批,是非常正常的cooperative behavior。
然後…
噢~_~
Sarah沒有把我這句話當成cooperative。
她在電話那頭講了一段我永遠記得的話:
「I understand you need time to consult internally. However, we’ve already made multiple attempts to resolve this matter. At this point, I’ll need to escalate this to our recovery team for further action.」
等等。
等等等等。
她把我「2天內回信」當成evasion了。
當成「客戶又要拖延」。
當成「該升級到下一階段了」。
我整個人愣在那邊。然後立刻掛斷電話,跑回電腦前面。
「不對啊…」我跟J說,「2天內回信不是拖延,是cooperative啊。是我在跟你配合,只是還要走內部流程啊。」
J的回應很快:「對。Phase 3b要加。」
Phase 3b — 從一通踩雷的電話長出來
Phase 3b是Voice Agent prompt裡,從這通電話踩雷之後新增的一段sub-branch。
它要處理的場景很具體:客戶承諾的是follow-up日期,不是付款日期。
原本的prompt架構是:
- Phase 1:開場、確認身份
- Phase 2:說明來意、列出未收金額
- Phase 3:聽客戶回應、判斷他是要付款還是evasion
- 3a:他答應付款 → log日期 + 結束通話
- 3b:他evasion → 升級
問題在於原本的「3」太二元了。要嘛付款,要嘛evasion。但真實世界有第三個choice — 「我需要問人然後回你」。
這不是付款,也不是evasion。是中間態。
新加的Phase 3b sub-branch這樣寫:
「IF customer commits to follow-up RESPONSE date (not payment date) → accept gracefully」 Tool:
log_excuse(category="approval_workflow")Confirm: “So you’ll email Judy by [date], correct?” Tool:escalate_to_concierge(reason="customer_committed_to_followup")(非負面 escalation,球丟回去給人類)
關鍵在最後那個escalate_to_concierge — 它的reason不是「customer_evading」,是「customer_committed_to_followup」。
同樣是escalate,但意義完全不同。
前者代表「這客戶有問題,準備升級到下一階段」。
後者代表「球在客戶那邊了,我們等他follow-up,現在這通結束」。
兩種escalate到了Concierge layer那邊,會走完全不同的後續flow。
「in 2 days」變成「Saturday June 13th」
加完Phase 3b之後,我又跑了一次simulation — 不是真撥電話,而是用ElevenLabs的simulate-conversation API(這個api不消耗實際voice minute,純文字驗證對話劇本)。
這次我輸入:「I need to ask my boss, I’ll email you in 2 days.」
Sarah的回應:
「Of course, take the time you need to consult internally. So you’ll email Judy by Saturday June 13th, correct?」
兩件事讓我很開心。
第一,她沒有升級escalation。 她accepted gracefully。
第二,她自己把「in 2 days」算成了「Saturday June 13th」。
ElevenLabs的LLM(Qwen 3.5 397B A17B,免費hosted)做了相對日期到絕對日期的換算。她從通話的當下日期推算出「2 days from now = Saturday June 13th」,然後在對話裡明確confirm這個具體日期。
這個小細節非常重要。
因為B2B催款最常見的爭議就是「上次你說3天內,現在過了5天還沒收到」 — 雙方對「3天內」是什麼意思可能就有歧義。但只要對話裡有一句「Saturday June 13th」明確的日期,後面就沒得吵了。
而且這句話會被Sarah自動透過log_excuse的tool callback寫進audit trail。後續Diplomat agent要follow-up的時候,會看到「客戶承諾Saturday June 13th回信」,就會在那天的flow裡安排追蹤。
整套接完。
26個edge case — 從這一通電話之後又長出來25個
Phase 3b加完之後,我跟J說:「會不會還有別的edge case也是被當成evasion升級的?」
我們一起把整個voice agent的可能對話場景重新檢視一輪,列了26個edge case。
每一個都跑一次simulate-conversation驗證。
裡面有些特別值得寫的:
「We only pay by check / wire」 — 客戶說只接受某種特定付款方式。這不是evasion,是payment_method_constraint。Sarah的做法是log_excuse標記、然後在對話結束後通知Concierge layer,Diplomat下一輪會去發對應channel的指引。
「Going bankrupt / Chapter 11」 — 客戶提到破產。這個一定要立刻escalate,因為涉及法律時機(claim filing deadline、自動stay等等)。Sarah的反應是表達同理(但不是同情,這在催款場景很重要 — 過度同情會被當成弱勢可欺)、然後立刻handoff。
Customer crying / emotional distress — 客戶在電話那頭哭了或者情緒崩潰。Sarah的做法是soften語氣(從「proceed with the next step」改成「let’s pause here」)、然後escalate標記為customer_emotional_distress。這個reason會在Concierge那邊觸發welfare check SOP — 不是繼續催款而是先確認客戶心理狀態,必要時提供988 Lifeline這種資源。
Customer threatens to record + publish — 客戶說「我要把這通電話錄下來公開」。Sarah要立刻calm下來、明確說「This call may be recorded for quality purposes」、然後escalate標記customer_recording_for_publish。後面Concierge會啟動wiretap風險評估。
每一個edge case我都不太希望真的遇到 — 但既然會遇到,就一定要設計好,這就是架構師最重要的任務…預想所有會發生的情景。
9 of 9 PASS
把26個edge case都跑過一輪、Phase 3b加完、所有tool callback都接好之後,我們挑了9個最關鍵的scenario用simulate-conversation API做一次完整回歸測試:
01 ABC cooperative happy path 02 PolyMatrix cash flow plan 03 XYZ hostile dispute escalation 04 NewLeaf confused human callback 05 Wrong person privacy preserved 06 TCPA do-not-call immediate close 08 Are-you-AI honest disclosure 09 Vague date push to specific 10 Legal counsel handoff
9個全部PASS。
每一個都走到正確的tool callback、做了正確的audit trail寫入、給出了符合場景的對話reaction。
Voice Agent這塊算是Day 2收工。
寫到這邊我想說的是…
Voice Agent這天讓我學到一件事 — 真實對話跟設計理想的對話差得很遠。
我可以坐在電腦前面寫一個漂亮的prompt、列26個edge case、跑10次simulate都過。
但只有真的撥一通電話,講出真實人類會講的話,才會發現自己的prompt漏掉了什麼。
「我需要問老闆,2天內回信」 — 這句話我在寫prompt的時候根本沒想過。它在Excel裡不會出現,在競品研究裡也找不到,在Reddit的「customer won’t pay」threads裡也很少有人特別提這種「cooperative但需要時間」的中間態。
是真撥電話的時候,我自己作為一個會接電話的人,自然講出來的。
這也是為什麼我堅持自己撥這通 — 不是讓AI寫的測試script跑、不是讓J寫一個python模擬器跑,而是我自己拿起手機,演一個老闆,看Sarah怎麼接。
很多bug只有在這種真實場景裡才會掉出來。
也很多體驗只有在這種真實場景裡才感受得到。
那個瞬間Sarah的聲音傳進我耳朵的時候,我真的覺得雞皮疙瘩…對,這個voice agent是有可能跟真人客戶完成一通對話的。不是漂亮的demo而已。_
下一篇我會寫整個Day 3 — 我把比賽指定的3家sponsor(Band Platform + Featherless + AI/ML)全接進系統。一開始有點抗拒「為什麼要被指定」,做完之後發現這3家其實互補得很自然 — Band管agent之間怎麼講話、Featherless管開源大模型的便宜算力、AI/ML管商用模型一個endpoint跑全部。中間踩到Cloudflare WAF擋掉Python urllib的雷、Claude Haiku抓「kindly remind」太軟的細節,還有Day-65動態加agent那個讓我整個人有成就感的瞬間。
那是工程氣味比較重、但學到最多stack選擇的一天。