前面幾天掛上去的工具,tools/list 列得出來、連線測試也通過。這些只證明工具到得了模型面前。實際跑一次「關閉前後門風扇」,模型花了三分鐘做別的事,該用的那個工具一次都沒有呼叫。今天拆這次失敗,以及它帶出來的工具設計問題。
當時的架構是兩層,上層收到指令後委派給一個下層 worker,風扇控制工具掛在 worker 身上。那三分鐘的動作統計:
| 動作 | 次數 |
|---|---|
查知識庫 iot_kb |
7 |
| 檔案系統操作 | 8 |
呼叫 control_fans |
0 |
最後收到的是 Error: Model generated invalid tool call: transfer_task。
工具在工具清單裡,模型選擇了別的路徑。 三件事同時成立才湊出這個結果:
fan_0 與 fan_1,模型無從判斷哪個是前門知識庫用的是 bm25 關鍵字檢索。pkg/rag/strategy/bm25.go 的 tokenize() 做四件事:
strings.ToLower 轉小寫strings.Replacer 把標點換成空白strings.Fields 依空白切成 token第二步那個替換表列了十六組,內容是 . , ! ? ; : 括號四種、引號兩種,加上換行與 tab,全部是 ASCII。中文的全形標點不在表上,中文句子裡也沒有空白,一整句中文切完之後是一個巨大的 token。檢索詞與文件內容除非逐字一模一樣,否則對不上。模擬檢索的結果是中文詞 0/5 命中、英文詞 5/10 命中。
這帶出知識庫的兩條寫作規則:
**grafana 與 grafana 是兩個不同的字串,關鍵詞要在表格或程式碼區塊裡有一個沒加粗的出現位置檢索工具這一側的修法是在 instruction 裡明寫檢索詞用英文或 ASCII 關鍵字,查不到就換詞重查。治本的做法是換成 embedding 或混合檢索。
| 修正 | 拆掉的是 |
|---|---|
| 工具從 worker 移到上層,取消這一段委派 | 委派過程中任務描述失真 |
| 不給那一層知識庫與檔案工具 | 「先查知識庫」這條指示沒有對象可以執行 |
工具描述裡寫明 fan_0 是後門、fan_1 是前門 |
指令用語與參數之間缺一段對照 |
三個一起上之後,迷路這個現象消失。
第三條是工具設計層面的問題,與這次故障的其他兩條性質不同。工具的參數名取自底層 API,底層 API 取自硬體編號,而人下指令時說的是前門與後門。這段對照要嘛寫進工具描述,要嘛模型自己猜。
這也是自建 MCP 那兩天把工具切成 control_door_fans 而非 set_gpio(pin, value) 的理由。語意層的工具名把對照關係固定在協定裡,低階參數工具則把它留給模型。
工具的命名與描述屬於送進模型的 prompt,它決定模型有沒有辦法把人的說法對應到參數
改完之後重跑,正確的行為長這樣:先讀感測狀態,接著反問是哪一台設備,印出前後門與欄位的對照,在得到答案之前沒有動任何風扇。同一個情境連跑十次,十次都是這個流程。
驗證方法這一側有個細節,問一個能從常識答出來的問題時,模型即使跳過工具也可能答對。兩種證據的強度差距很大:
| 證據 | 證明得了什麼 |
|---|---|
| 回答看起來正確 | 模型產出了合理的文字 |
| 回答裡帶著只有真實資料才有的讀值 | 那個值來自一次實際取得 |
日誌裡出現 tool=<工具名> |
工具被呼叫過,次數可數 |
查詢的內容要限定成只有真實資料答得出來的細節,例如某個具體讀值、某台設備目前的吹向。日誌那一條最直接,工具名出現幾次與回答像不像無關。
同一段時間還遇到一個與模型無關的失敗。stdio 型的 MCP server 以 npx 啟動,在 Windows 上回:
mcp(stdio cmd=npx ...) start failed: server unavailable
手動在終端機跑同一個 npx 指令正常。差別在於終端機是 cmd shell,它會自動補副檔名找到 npx.cmd,而原生程式直接 spawn 沒有副檔名的 npx 找不到可執行檔。
兩個修法:
cmd /c 包一層,command: cmd、args: ["/c", "npx", "-y", "<套件>"],讓 cmd.exe 負責解析走容器的 MCP 不受這個問題影響,它 spawn 的是 docker 而非 npx。
最初的判斷方向是模型能力不足,於是去直接打推論端點,帶著同一份工具定義送一次請求。回來的是合法的 tool call,finish_reason 是 tool_calls,格式與內容都對。
那個結果把問題範圍縮掉一大半。模型選得出工具,選不出來的是在那個情境底下。差別在於直接打端點時,送過去的只有工具定義與那句指令,而實際跑的時候前面還有一段 instruction 要它先查知識庫。
同一個模型、同一組工具,前面多一段指示,行為就換一套。工具清單決定它能做什麼,前面那段文字決定它會先做什麼。
工具到得了模型面前,與模型會不會用它,由 prompt 裡的不同位置分別決定
四個指令名字裡帶 serve 或 peer,只有一個是 agent 對 agent。