六個工具:get_instructions、search_notes、read_note、create_note、update_note、list_folder。
看起來就是一組 CRUD,但每一個名稱、每一句描述、每一則錯誤訊息,都在教 agent 怎麼做事。今天講的其實不是 API 設計,是提示詞工程,只是它偽裝成 API 設計。
模型看不到你的原始碼,它只看得到工具的名稱、描述、參數說明。那幾行字就是你唯一的指揮權。
比較一下這兩種寫法:
// 寫給人看的
description: '取得指令內容'
// 寫給模型看的
description: '回傳 schema/ 層的 AI 編纂指令全文(等同知識庫的 CLAUDE.md)。動筆前先呼叫本工具。'
第二種多了三件事:回傳的是什麼層的什麼東西、什麼時候該呼叫,以及一個類比。括號裡那句「等同知識庫的 CLAUDE.md」不是寫給使用者看的,是寫給模型看的。它讀得懂 CLAUDE.md 是什麼,一句話就知道該用什麼態度對待這一層。
create_note 的描述反而寫得很保守:
description: '在指定 path 建立新筆記。path 須以 raw/、wiki/ 或 schema/ 開頭並以 .md 結尾;已存在則回錯誤(不覆蓋)。'
它只講規則,沒講碰到規則之後該怎麼辦。那句話我放在別的地方,放在錯誤訊息裡:
throw new NoteError('CONFLICT', {
'zh-TW': `筆記已存在:${path}(請改用 update_note 並帶 if_version)`,
en: `Note already exists: ${path} (use update_note with if_version instead)`,
});
括號裡那半句才是真正在指揮模型的東西。它預先寫好了「碰到這個錯誤時該怎麼辦」,所以真的碰到的時候,agent 不需要猜。我在實測看到的行為正是如此:create_note 碰到 CONFLICT 之後,agent 讀完錯誤訊息就自己改用 update_note,沒有卡住,也沒有重試三次。
它不只是給人看的診斷,是給模型看的下一步指示。
而且錯誤訊息比工具描述便宜。工具描述每一步都要重送一次給模型,寫得越細、每一步越貴;錯誤訊息只在真的碰到的那一次才出現。所以「平常該怎麼做」寫在描述裡,「碰壁之後怎麼辦」寫在錯誤訊息裡,把話講在最省的地方。
規則放在知識庫裡而不是系統提示詞裡,這個取捨之前我們已經算過帳了。但它留下一個壞處沒解決:模型不會自己知道要去讀那一層。
工具設計就是用來補這個洞的,補在三個地方:
三層都不是強制的,沒有任何程式碼擋住「不讀規則就 create_note」這條路。實測下來 agent 幾乎每次都會先讀,但那是被引導出來的行為,不是被保證的行為。這是工具設計跟權限檢查最根本的差別:描述只能影響意圖,擋不住行為。真的不能發生的事,還是得寫成程式碼。
之前我們已經看過產生待編纂清單的那幾行程式。從 agent 的角度,它呼叫 get_instructions 拿到的東西是這樣開頭的:
## 待編纂的來源(3)
以下 raw/ 來源還沒有任何 wiki/ 頁連回它,請依規則的 Ingest 步驟處理,做完每一則都要在 wiki/log.md 追加紀錄:
- raw/sources/mensh2017ten.md(Ten simple rules for structuring papers)
…
規則被推到這份清單後面。這個順序是刻意的:agent 一進來先看到「有三件事待辦」,再看到「該怎麼做」。使用者只要說「幫我整理一下」,它就找得到該做的事,不需要指定檔案路徑。
那句「做完每一則都要在 wiki/log.md 追加紀錄」也是刻意重複的。它跟著待辦清單一起出現,比寫在規則頁的第七行更難被忽略。
六個工具的名稱、描述與錯誤訊息,是我唯一能指揮模型的地方。描述講平常怎麼做,錯誤訊息講碰壁之後怎麼辦,兩邊加起來就是這個產品的提示詞。工具只有六個也是同一套算計:每一次呼叫都要把所有描述重送一次,工具越多、每一步越貴。
這份東西歸誰改也分得很清楚。介面與錯誤訊息是我的,改了所有人跟著改;規則頁是使用者的,他改完不必等我發版,我改版也不會動到他寫的那幾行。