自動化與 Agent
AI Agent「空回應」除錯清單:從症狀到根因的 5 個步驟
模型能聊天,不代表它能呼叫工具。看到「空回應+工具呼叫次數 0+無錯誤訊息」,先換模型,再改 prompt;用繞過框架的分離實驗,一次切開變因。
一個核心觀念(最反直覺的一個)
模型能聊天,不代表它能呼叫工具。這是兩種不同的能力。
多數人的直覺是:模型回話很正常,所以模型沒問題,一定是我的 prompt 或設定壞了。這個推論是錯的。同一顆模型可能:
- 對話流暢 ✅
- 呼叫「唯讀類」工具正常 ✅
- 但要生成「執行指令類」的工具呼叫時,直接吐空 ❌
所以看到下面這組症狀,第一個該換的是模型,不是 prompt:
空回應(沒有文字也沒有推理內容)+ 工具呼叫次數為 0 + log 裡沒有任何錯誤或限流訊息
順序反過來,你會花好幾個小時改 prompt 卻毫無進展,因為 prompt 從頭到尾都是對的。適用 Hermes、LangChain、AutoGen 或自寫的 agent 迴圈。
按步驟做
Step 1:先確認你看的是「當前」的設定
改完設定檔後一定要重啟服務。常駐型的 agent(gateway、daemon、背景服務)把設定和提示詞讀進記憶體後就不會再讀檔。比對「服務的啟動時間」和「設定檔的最後修改時間」,如果服務比設定檔還早啟動,它跑的是舊的。
Step 2:讀 log,抓三個關鍵數字
不要看聊天畫面,看 log:
- 這一輪呼叫了幾次 API
- 工具呼叫次數(
tool_turns或類似欄位),最關鍵的一個 - 有沒有 429 或其他錯誤碼
判讀:工具呼叫次數 = 0,代表模型根本沒發出工具呼叫,問題在「模型願不願意、能不能生成呼叫」;有 429 是限流,跟本篇無關(限流會明確告訴你);完全沒有錯誤但回應是空的,繼續往下走。
Step 3:確認工具真的有啟用
多數框架都有列出「當前啟用了哪些工具」的指令。先確認你以為在的工具真的在清單裡。這步 30 秒,排除低級錯誤,後面的推論才站得住。
Step 4:做「分離實驗」(決勝的一步)
目的:把「框架的問題」和「模型的問題」一刀切開。做法:繞過整個 agent 框架,用最原始的 HTTP 請求直接打模型 API,帶上一模一樣的 system prompt 和工具定義,問一模一樣的問題。
Python 骨架(以 OpenAI 相容格式為例,其他家改 URL 和欄位名即可):
import json, os, urllib.request
API_KEY = os.environ["YOUR_API_KEY"] # 從環境變數讀,不要寫死在檔案裡
SYSTEM_PROMPT = open("your_system_prompt.md", encoding="utf-8").read()
# 只放一個最簡單的工具,複製你框架裡那個「壞掉」的工具定義
TOOLS = [{
"type": "function",
"function": {
"name": "terminal",
"description": "Execute a shell command and return its output.",
"parameters": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"],
},
},
}]
body = {
"model": "你的模型名稱",
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": "用 terminal 執行 whoami 並把結果給我"},
],
"tools": TOOLS,
}
req = urllib.request.Request(
"https://你的模型端點/v1/chat/completions",
data=json.dumps(body).encode("utf-8"),
headers={"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}"},
)
with urllib.request.urlopen(req, timeout=60) as r:
d = json.loads(r.read().decode("utf-8"))
# 關鍵:看有沒有 tool_calls,以及 finish_reason 是什麼
msg = d["choices"][0]["message"]
print("finish_reason:", d["choices"][0].get("finish_reason"))
print("有沒有工具呼叫:", "tool_calls" in msg and bool(msg["tool_calls"]))
print(json.dumps(msg, ensure_ascii=False, indent=1)[:800])
判讀結果:
| 原始 API 的結果 | 結論 | 下一步 |
|---|---|---|
| 正常回傳 tool_calls | 不是你的 prompt 的錯,問題在框架或模型組合 | 換模型(Step 5) |
| 也是空的、沒有 tool_calls | 問題在 prompt 或工具定義本身 | 回頭簡化 prompt、檢查工具 schema |
| 回傳 4xx 錯誤 | 認證、格式或額度問題 | 照錯誤訊息處理,通常很明確 |
分離實驗的價值:不管結果是哪一種,你都能一次排除掉一半的可能性。逐一微調 prompt 的問題是,每次改動都同時改變多個變因,就算症狀變好你也不知道為什麼。
Step 5:換模型,然後重跑驗收
如果 Step 4 顯示模型有能力生成工具呼叫、但框架裡就是不行,換一顆模型試。優先選同系列的新一代(相容性最好,通常改一行設定)。換完務必重啟服務(回到 Step 1),再重跑驗收測試。
你一定會踩的坑
坑 1:改了提示詞卻沒生效,因為服務沒重啟
你會以為新規則沒用,其實是根本沒載入。任何常駐服務,改完設定就重啟,養成反射動作。
坑 2:新舊規則在同一份提示詞裡打架
一份 system prompt 的中段寫「狀態查詢你自己回答就好」,末段(後來補上的新規則)寫「狀態查詢要委派給別的工具處理」。模型讀到前段就自己回答了,新規則完全失效。教訓:往長篇提示詞增補新規則時,回頭掃一遍有沒有舊條款互相矛盾;另外,新規則放前段比放末尾有效,能力較弱的模型對開頭的服從度明顯較高。
坑 3:你的測試題根本驗不到東西
一道驗收題問「你的行為規則放在哪裡?」,但提示詞裡從來沒有規定過該怎麼回答,模型答錯其實不算違規,那道題什麼都沒測到。教訓:每一道驗收題都必須對應到規範裡的一條明文,沒有對應條款,先補條款再出題。
坑 4:把「限流」誤判成「空回應」
- 限流(429):錯誤訊息很明確,會告訴你上限是多少、要等幾秒。
- 空回應:完全沒有錯誤訊息,log 一片乾淨。
看到 429 就是配額問題,不要往模型能力方向查;反之亦然。
附錄:關於配額,兩個很多人搞錯的觀念
① RPM 是速率,不是總量。RPM = Requests Per Minute。「限額 5」的意思是同一分鐘內最多打 5 次,每分鐘重置、不會耗盡。看到數字很小就急著付費之前,先確認單位是 per minute 還是 per day。
② RPM 通常是「每個模型」獨立計算的。所以你可以設一條 fallback 鏈:主力模型撞到限流時,自動改打另一顆。兩顆模型各有各的配額,等於免費把可用量翻倍。設定後務必實測:用併發請求把主力打到限流,立刻再問一次,確認真的由備援接手,不要只看設定檔寫對了就當作完成。
⚠️ 加新模型進 fallback 鏈之前,先用 Step 4 的腳本驗證它有能力生成工具呼叫,否則你只是加了一顆「會聊天但不做事」的備援,故障時反而更難查。
萬用救援:卡住怎麼問 AI
上面任何一步卡住,把下面這段複製給 AI(ChatGPT、Claude 都可以),把方括號換成你的情況:
我在除錯一個 AI agent,它會正常回話但不執行任何動作。
我的環境:
- 框架/工具:[例如 Hermes / LangChain / 自己寫的]
- 模型:[模型名稱]
- 作業系統:[Windows 11 / macOS / Ubuntu]
症狀:
[貼上完整的 log 片段,包含錯誤訊息原文,不要自己改寫]
我已經試過:
[列出你做過的事]
請一步一步帶我排查,每次只給我一個指令或一個檢查動作,
等我回報結果再給下一步。我是新手,請說明每個指令在做什麼。
重點:貼 log 要貼原文,錯誤訊息裡的每個字都是線索;並要求 AI「一次只給一步」,否則它會一口氣給你十個指令,你不知道是哪一步解決的(跟分離實驗是同一個道理)。
一句話總結
沒有錯誤訊息的故障,要用「能一次切開變因的實驗」去查,而不是逐一微調可疑處。在 AI agent 的情境裡,那個實驗就是「繞過框架,直接用原始 API 打一次」。
這是踩坑實錄與 AI 工具實測系列的一部分。想收到後續完整 SOP,訂閱電子報。
支持這裡的內容:前往 Ko-fi 斗內