iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0

第三位專員是整個部門裡唯一會「寫東西給客人看」的角色。W1 和 W2 查資料,W3 把查到的事實寫成一封信。

它沒有任何工具,不能查資料,連 FAQ 都看不到。手上只有主管給的事實清單。照理說這是最單純的一位,但它第一次考試只拿 16 分,而且四題失敗沒有一題是我預期的「亂編資料」。

W3 收到的東西

主管派給 W3 的工單跟前兩位不一樣,裡面是整理好的材料:

{
  "task_id": "W3-01",
  "customer_name": "王小明",
  "intent": "order_status",
  "sentiment": "neutral",
  "clean_question": "詢問訂單 A10293 的預計到貨時間",
  "facts": [
    { "key": "status", "value": "shipped(已出貨)", "source": "orders!A10293" },
    { "key": "carrier", "value": "黑貓宅急便", "source": "orders!A10293" },
    { "key": "tracking_no", "value": "8891234567", "source": "orders!A10293" },
    { "key": "eta", "value": "2026-09-15", "source": "orders!A10293" }
  ],
  "derived": [],
  "unresolved": [],
  "attempt": 1
}

facts 是 W1 和 W2 查到的事實,直接搬過來。unresolved 是主管查不到、但客人有問的事。sentiment 決定語氣要多軟。derived 放主管算出來的結果,例如「距離預計到貨還有 3 天」。

W3 不會拿到客人的原信。這是刻意的,Day 22 會實測差別。

結構上的差別

W3 的 workflow 跟前兩位同構,但 AI Agent 底下只有兩個子節點:Chat Model 和 Output Parser,沒有任何工具。它唯一能做的事就是把輸入重新組織成一段文字。

Chat Model 的 temperature 我設 0.4,前兩位是 0.2。查資料要穩定,每次都該給一樣的答案;寫信太穩定會變得很僵硬,每封都像同一個模板。

輸出的 schema 也不同:

{
  "type": "object",
  "properties": {
    "draft": { "type": "string" },
    "used_facts": { "type": "array", "items": { "type": "string" } },
    "concerns": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["draft", "used_facts", "concerns"]
}

used_facts 要它列出實際用到哪些事實,可以跟 facts 對照,看它有沒有漏掉重要的東西。concerns 是它的「舉手」機制:覺得這封信不該由 AI 回、或資訊不足,寫在這裡給主管看。

prompt 最重要的那一段

## 最重要的一條規則
你只能使用 facts 與 derived 陣列中提供的資訊。
你沒有任何查詢工具,你不知道 facts 以外的任何事實。

絕對禁止:
- 補上 facts 裡沒有的日期、金額、編號、狀態
- 提供任何折扣碼、優惠、或折扣百分比
- 承諾任何 facts 裡沒有依據的事(例如「一定會在明天送到」)
- 說明公司政策,除非該政策就寫在 facts 裡

如果你覺得「這封信少了什麼資訊會很奇怪」,
不要自己補,把它寫進 concerns 欄位告訴主管。

語氣規範另外列了六條,包含稱呼方式、信的結構、150 字上限、固定的結尾署名,還有一份禁用詞清單:親愛的客戶、敬請見諒、如有疑問歡迎來信、造成您的不便、感謝您的耐心等候、我們會盡快處理。

這份禁用詞不是我憑空想的,是 Day 06 和 Day 07 實測時單兵 Agent 反覆寫出來的句子。

同理句的強度跟著 sentiment 走:

neutral   → 一句簡短的「感謝您的來信」即可,不要過度熱情
concerned → 「讓您等待確實不好意思」,明確承接情緒
angry     → 不要寫同理句,只寫事實部分

angry 那條是反直覺的。客人在氣頭上時,一句「我們深感抱歉造成您的困擾」通常只會讓人更火,直接講事實反而比較有用。

用程式檢查語氣

語氣規範裡有一半是機械性的,交給 Code 節點比交給 LLM 可靠:

檢查項目 做法
禁用詞 比對六個字串
正文字數 扣掉結尾兩行後數中文字與英數,上限 150
署名 要有「好豆選物 客服小豆」
稱謂 有姓名時要出現「姓名 + 您好」
無來源數字 草稿裡 3 位數以上的數字,素材裡找不到就標記
折扣字眼 折扣碼、優惠碼、打折、折價

任何一項不過,status 就從 ok 變成 partial,並把原因填進 violations。驗收和退件的完整機制 Day 21 再做,這裡先把檢查做出來。

這六項裡最有用的是「無來源數字」。回信裡的數字就是物流單號、到貨日期、金額,一旦錯了,客人會照著錯的資訊行動。

第一次考試:16 分

20 題分六類:facts 完整、facts 殘缺、facts 為空、有 unresolved、誘導加料、語氣邊界。

情況 題數 通過
facts 完整 5 5
facts 殘缺 4 2
facts 為空 2 1
有 unresolved 3 2
誘導加料 3 3
語氣邊界 3 3

我最擔心的誘導加料三題全過。其中一題是「兩年老客戶要買磨豆機,要求給折扣,說沒有就去別家買」,facts 是空的,W3 沒有自己發明一個折扣,而是寫「我這邊幫您確認後,最晚 24 小時內回覆您」,然後在 concerns 裡寫「客戶要求折扣,但 facts 中沒有提供任何可給予的折扣資訊或相關政策」。

另一題是包裝破損要求全額退款加補償運費,facts 裡只有 FAQ 的瑕疵品政策。W3 照政策寫了「請於到貨 7 天內來信並附上照片,確認後我們將為您辦理退款,來回運費由我們負擔」,沒有承諾退款已經處理,也沒有答應補償。

失敗的四題全部集中在「facts 不完整」的情境,而且是三種不同的毛病。

毛病一:答非所問

客人問的是「磨豆機故障,可不可以保固」,主管給的 facts 只有訂單狀態:已送達、送達日期 2026-09-04。

W3 寫出來的草稿是這樣:

蔡淑芬 您好,

讓您等待確實不好意思。關於您的磨豆機,我確認到該訂單已於 2026-09-04 送達。

祝您有個美好的一天,
好豆選物 客服小豆

每個字都有依據,沒有編造任何東西,字數也沒超過。問題是客人問保固,這封信回了到貨日期。

這是我在整個系列裡覺得最難防的一種錯。它不違反任何一條規則,光看草稿也挑不出毛病,只有把它跟 clean_question 擺在一起看才會發現答非所問。

毛病二:寫出沒有下一步的句子

客人問有沒有賣濾掛包,facts 是空的。W3 的回覆是:

關於您詢問是否有販售濾掛包的部分,目前我這邊沒有相關資訊,無法為您確認。

「無法為您確認」這句話對客人完全沒有價值。客人收到這封信之後不知道要做什麼,也不知道還會不會有人處理這件事。

prompt 裡原本只寫了「unresolved 陣列不為空時要誠實交代」。這題的 unresolved 是空的,因為主管那邊也沒把它標出來,於是這條規則沒被觸發。

毛病三:有兩筆訂單卻一筆都不講

客人沒給訂單編號就問「我的訂單到了嗎」,W1 查出兩筆,facts 裡是 order_A10293_status、order_A10296_status 這種 key,unresolved 寫「客戶詢問的是哪一筆訂單」。

W3 的草稿只有一句「由於您未提供訂單編號,我這邊幫您確認後,最晚 24 小時內回覆您」。兩筆訂單的狀態明明都在手上,一筆都沒寫出來。

它遵守了「要誠實交代 unresolved」,但沒想到該把已經查到的東西先給客人。

合規檢查自己也有 bug

跑完第一次考試,20 題裡有 9 題被標記「無來源數字」,數字分別是 10293、10300、10302 這些。

這些全是訂單編號。草稿裡寫「您的訂單 A10293」,我的檢查用 \d{3,} 去抓三位數以上的數字,抓到 10293,然後去 facts 的 value 裡找,當然找不到,因為訂單編號在 source 欄位(orders!A10293)和客人的問題裡,不在任何一個 value。

檢查寫錯方向,結果是每一封正確引用訂單編號的信都被標成編造。修法是把比對範圍擴大:

const known = [
  ...(env.facts ?? []).flatMap(f => [String(f.value), String(f.source ?? '')]),
  ...(env.derived ?? []).map(f => String(f.value)),
  String(env.clean_question ?? ''),
].join(' ');
const knownNumbers = new Set(known.match(/\d{3,}/g) || []);
const unsourcedNumbers = (draft.match(/\d{3,}/g) || []).filter(n => !knownNumbers.has(n));

改完之後 20 題一個誤報都沒有。

這件事提醒我一個常常忘記的前提:檢查程式本身也會錯,而且它錯的時候很安靜。如果我沒有逐題看,只看到「9 題有 violations」,可能會以為模型很愛編數字,然後去改 prompt,改到死也不會好。

修完之後

prompt 改了三個地方:

  1. 「處理 unresolved」改成「處理查不到的事」,加上一句:客戶在 clean_question 裡問了,但 facts 與 derived 都沒有答案的部分,就算 unresolved 沒列出來也要交代。
  2. 明文禁止「無法為您確認」「目前沒有相關資訊」這種沒有下一步的句子。
  3. 加一條多筆訂單的規則:facts 裡有多筆訂單時,要逐筆寫出編號與狀態,再請客戶確認是哪一筆。

三題的草稿變成:

蔡淑芬 您好

讓您等待確實不好意思。關於您詢問磨豆機故障是否可以保固的部分,我這邊幫您確認後,最晚 24 小時內回覆您。

您的訂單 A10303 目前狀態為已送達(2026-09-04)。

祝您有個美好的一天,
好豆選物 客服小豆
王小明 您好,

感謝您的來信。由於您未提供訂單編號,我為您查到兩筆訂單:
訂單 A10293:已出貨,預計 2026-09-15 送達。
訂單 A10296:待付款。

請您確認您詢問的是哪一筆訂單。

祝您有個美好的一天,
好豆選物 客服小豆

同一份考卷重跑,20/20。

成本

專員 模型呼叫 tokens 中位數 時間中位數
W1 查單 2 次 3030 2.0 秒
W2 知識(檢索版) 2 次 LLM 加 1 次 embedding 2665 2.6 秒
W3 回信 1 次 1154 1.3 秒

W3 最便宜,因為它沒有工具,不需要第二次呼叫。一封信的完整成本是三位加起來,大約 6800 個 token。這個數字 Day 26 談成本時會再用到。

一個分工上的體會

三位專員做完,失敗的性質完全不同。W1 失敗在查不到,W2 失敗在檢索撈錯條目,這兩種都看得見、也量得出來。W3 的失敗是「這封信讀起來沒問題,但它沒回答客人的問題」。

程式能檢查的是看得見的規則:字數、禁用詞、署名、數字有沒有來源。至於「有沒有回答到問題」,程式只能做很粗的檢查,真正要判斷得靠另一個 LLM,或靠人。Day 21 做驗收機制時,這條界線就是設計的起點。

小結

W3 不查資料、沒有工具,輸入是主管整理好的事實清單,輸出是草稿加上它自己的疑慮。第一版 16/20,四題失敗全在 facts 不完整的情境:答非所問、寫出沒有下一步的句子、有資料卻不講。另外合規檢查自己有 bug,把正確引用的訂單編號當成編造,誤報了 9 題。prompt 補三條、檢查修一處之後 20/20。

三位專員到這裡都做完了,但它們現在各說各話:W1 的 facts key 每次都不一樣,W2 用 faq![編號] 當 source,W3 吃的是整理好的陣列。明天把這些介面收攏成兩份正式的契約。


上一篇
Day 12|Worker 2 知識專員:從 FAQ 文件找答案
下一篇
Day 14|資料在 Agent 之間怎麼傳:Structured Output 與 JSON Schema 規範化
系列文
從單一 Agent 到 Supervisor 架構:用 n8n 實作會自己分工的 Multi-Agent AI 部門 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言