動手搭一個最小 AI Agent Harness:用偽代碼拆解那個 while 迴圈(實作篇)

最後更新: · AI
AI Agent 的核心 while 迴圈:問模型 → 執行工具 → 回填 → 再問

上一篇〈AI Agent Harness 是什麼〉 裡,我們用白話建立了一個核心觀念:Agent = Model + Harness。模型負責「想」,harness 負責「動」——它在一個迴圈裡反覆呼叫模型、執行工具、把結果回填,直到任務完成。

這一篇,我們把那個「執行引擎」拆開來,一行一行看它到底怎麼運作。別緊張——我們不綁任何特定程式語言,全程用「偽代碼」(pseudocode,一種介於白話和程式之間、人人看得懂的寫法)來示範。你看懂了邏輯,就能用 Python、JavaScript 或任何語言把它寫出來。讀完這篇,你會發現一件事:一個能動的 AI Agent,核心其實短得驚人——大概 30 行就講完了。

(建議先讀完第一篇再回來,這篇預設你已經知道「代理迴圈」「工具」「停止條件」這些詞在講什麼。)

先看全貌:一個最小 Harness 長這樣

我們先把整個骨架攤開,讓你對「最小可運作的 harness」有個鳥瞰。下面這段偽代碼就是一個能跑的 agent 的全部核心——看不懂沒關係,接著我們會把它切成 6 塊,一塊一塊講清楚。

# ── 一個最小 AI Agent Harness 的全貌 ──

tools = [ ...工具清單,每個工具有名字、說明、參數規格... ]

messages = [ {role: "user", content: "幫我查台北現在幾度"} ]   # 對話歷史

迴圈 (最多 20 次):                         # ← 停止條件之一
    回應 = 呼叫模型(messages, tools)        # ① 問模型
    messages.加入(回應)                      # ② 把模型的話記進歷史

    如果 回應.stop_reason 不是 "tool_use":   # ③ 模型不想用工具了 → 完成
        跳出迴圈

    工具結果 = []                            # ④ 模型想用工具,逐一執行
    對每一個 回應裡的 tool_use:
        結果 = 執行工具(tool_use.name, tool_use.input)
        工具結果.加入( {tool_use_id: tool_use.id, content: 結果} )

    messages.加入( {role: "user", content: 工具結果} )   # ⑤ 把結果回填

印出 messages 最後的回答

就這樣。沒有魔法,就是一個 while 迴圈。剩下的篇幅,我們把這 6 個編號逐一拆解。

第 1 塊:定義工具(Tools)—— 寫給「模型」看的說明書

模型沒有手腳,所以第一件事是替它準備工具。每個工具的定義包含三個欄位(這是 Claude、OpenAI 等主流 API 通用的格式):

  • name(名字):模型用來「點名」要用哪個工具,例如 get_weather
  • description(說明):用白話告訴模型「這個工具能做什麼、什麼時候該用」。
  • input_schema(參數規格):用一種叫 JSON Schema 的格式,描述這個工具需要哪些參數、型別是什麼、哪些是必填。
工具 get_weather = {
    name: "get_weather",
    description: "查詢某個城市目前的天氣。當使用者問到溫度、下不下雨時使用。",
    input_schema: {
        type: "object",
        properties: {
            city: { type: "string", description: "城市名稱,例如 台北" }
        },
        required: ["city"]          # city 是必填
    }
}

這裡有個新手最容易輕忽、但極度重要的觀念:description 是寫給「模型」看的,不是寫給人看的。它就像你交辦工作給一個新同事時附的便條——便條寫得含糊,他就會做錯。Anthropic 在工程文章裡反覆強調:糟糕的工具說明,會把 agent 整個帶往錯誤的方向;把工具介面(他們稱為 ACI,agent-computer interface)設計好,往往比換一個更強的模型還有效。所以:把每個工具的 description 寫清楚、舉例說明何時使用,是你能對 agent 品質做的最高 CP 值投資。

第 2 塊:讓工具能真正「被執行」

定義只是「說明書」,模型讀了說明書後會說「我要用 get_weather,city 是台北」——但真正動手去查的是 harness。所以我們需要一個「派工」函式(dispatcher),把模型點名的工具名稱,對應到真正能執行的程式:

函式 執行工具(name, input):
    如果 name == "get_weather":
        return 真的去查天氣(input.city)      # 呼叫氣象 API,回傳例如 "台北 28°C 晴"
    如果 name == "send_email":
        return 真的去寄信(input.to, input.body)
    否則:
        return "錯誤:沒有這個工具"

看到了嗎?「工具」其實就是一段平凡無奇、你早就會寫的程式——查 API、寄信、讀檔案、跑計算。Agent 的神奇之處不在工具本身,而在於讓模型自己決定何時、用什麼參數去呼叫它們

第 3 塊:心臟——那個 While 迴圈

現在進到整個 harness 最關鍵的部分。請慢慢讀這段,它就是「AI 會自己把事情做完」的全部秘密:

迴圈 (最多 20 次):
    回應 = 呼叫模型(messages, tools)        # 把目前所有對話 + 工具清單丟給模型

    messages.加入(回應)                      # ⚠️ 關鍵:模型的回應(含它要用的工具)也要存進歷史

    如果 回應.stop_reason 不是 "tool_use":   # 模型說「我講完了」
        跳出迴圈                              # → 結束,回應裡就是最終答案

    # 跑到這裡,代表模型要求使用工具
    工具結果 = []
    對每一個 回應裡的 tool_use:
        結果 = 執行工具(tool_use.name, tool_use.input)
        工具結果.加入({
            type: "tool_result",
            tool_use_id: tool_use.id,         # ⚠️ 必須對回模型那次呼叫的 id
            content: 結果
        })

    messages.加入({role: "user", content: 工具結果})   # 把結果回填,下一圈模型就看得到

這裡有三個一定要懂的細節,新手九成的 bug 都出在這:

  • ① 看 stop_reason 來決定要不要繼續。每次模型回應,都會附一個「停止原因」。如果是 "tool_use",代表「我需要你幫我跑個工具」;如果是 "end_turn",代表「我講完了,這就是答案」。迴圈的進與出,全看這個值。
  • tool_use_id 必須對得上。模型每次要求用工具,都會給那次呼叫一個唯一的 id。你回填結果時,必須用同一個 id 標記,模型才知道「這個結果是對應我剛剛問的哪一題」。對錯了,模型就會錯亂。
  • ③ 模型的回應也要存進歷史。很多人只記得把「工具結果」存進 messages,卻忘了把模型「要求用工具」那句話也存進去。少了它,對話歷史就斷了,模型會以為自己從沒要求過。

第 4 塊:對話歷史是怎麼「越長越長」的

前面一直提到 messages 這本「不斷加頁的筆記本」。它到底長什麼樣?關鍵是:每一輪都是 append(往後加),不是重寫。這樣模型每次都能看到完整的前情提要(因為模型本身沒記憶,全靠這本筆記)。一個完整的查天氣任務,messages 會這樣成長:

messages = [
  # ── 第 1 圈之前 ──
  {role: "user",      content: "幫我查台北現在幾度"},

  # ── 第 1 圈:模型決定用工具(這是模型回的,被 append 進來)──
  {role: "assistant", content: [ {type:"tool_use", id:"abc123",
                                   name:"get_weather", input:{city:"台北"}} ]},

  # ── 第 1 圈:harness 執行工具後,把結果回填 ──
  {role: "user",      content: [ {type:"tool_result", tool_use_id:"abc123",
                                   content:"台北 28°C 晴"} ]},

  # ── 第 2 圈:模型看到結果,給出自然語言答案,stop_reason = "end_turn" ──
  {role: "assistant", content: "台北現在大約 28 度,天氣晴朗 ☀️"}
]
# stop_reason 變成 "end_turn" → 迴圈結束

注意這個一來一回的節奏:user → assistant(要用工具) → user(工具結果) → assistant(最終答案)。工具結果是用 role: "user" 的身分回填的——因為對模型來說,「環境給它的回饋」就跟使用者說話一樣,是它接收外界資訊的管道。

第 5 塊:停止條件——別讓 AI 原地打轉燒光你的錢

你應該注意到迴圈開頭寫的是「最多 20 次」,而不是 while True(永遠跑)。這是絕對不能省的安全閥。一個最小 harness 至少要有這幾種停止條件:

  • 正常完成:stop_reason 變成 end_turn,模型主動宣告做完了。這是最理想的出口。
  • 達到迴圈上限:例如「最多 20 圈」。萬一模型鬼打牆、反覆呼叫同一個工具,這條線會強制喊停。Anthropic 明確建議要設「最大迭代次數」來維持掌控。
  • 成本/時間上限:累計花費的 token 或時間超過門檻就中斷。每一圈都是一次付費的模型呼叫,沒有上限的迴圈=沒有上限的帳單。

記住第一篇講的那句話:AI 會犯錯、會鬼打牆。停止條件不是「以防萬一」,而是「一定會用到」的保險。

第 6 塊:錯誤處理——讓模型「看見自己的錯」

工具一定會出錯:API 斷線、查無此城市、參數格式不對。新手的直覺是「程式報錯就讓它崩潰」——但在 harness 裡,正確做法是把錯誤訊息「回填」給模型,並標記它是一個錯誤。為什麼?因為模型讀到錯誤後,常常能自己想辦法修正

對每一個 回應裡的 tool_use:
    嘗試:
        結果 = 執行工具(tool_use.name, tool_use.input)
        工具結果.加入({ tool_use_id: tool_use.id, content: 結果 })
    捕捉到錯誤 e:
        工具結果.加入({
            tool_use_id: tool_use.id,
            content: "執行失敗:" + e.訊息,
            is_error: true             # ⚠️ 告訴模型「這是一個錯誤」
        })

那個 is_error: true 旗標,就是成功結果和失敗結果唯一的差別。模型看到它,可能會:用修正後的參數重試、改問使用者要更多資訊、或老實說「這件事我做不到,因為……」。這種「讓模型看見錯誤、自我修正」的設計,正是好用 harness 和爛 harness 的分水嶺——爛的直接崩潰,好的讓 agent 自己爬起來。

完整跑一遍:看 messages 怎麼一圈圈長大

把六塊組裝起來,我們用「幫我查台北幾度,然後寄給老闆」這個需要兩個工具、兩圈的任務,看整個 trace:

  • 第 1 圈 問模型:它看到任務,回 stop_reason: tool_use,要求 get_weather(city:"台北"),id=aaa。
  • ↳ harness 執行:查到「台北 28°C 晴」,以 tool_use_id=aaa 回填。
  • 第 2 圈 再問模型:它看到天氣結果,回 stop_reason: tool_use,要求 send_email(to:"老闆", body:"台北現在 28 度晴"),id=bbb。
  • ↳ harness 執行:寄信成功,以 tool_use_id=bbb 回填「已寄出」。
  • 第 3 圈 再問模型:它看到信寄出了,回 stop_reason: end_turn:「已查到台北 28 度晴,並寄信給老闆 ✅」——迴圈結束

三圈、兩個工具、一個任務。每一個「決定下一步」都是模型做的,每一個「動手執行」都是 harness 做的。這就是你親手搭出來的、最小但完整的 AI Agent。

從「玩具」到「真實」:這個最小版缺了什麼?

上面 30 行的 harness 是真的能跑的——但離正式產品還有距離。一個成熟的 harness(像驅動 Claude Code 的那套引擎)還會補上這些:

  • 系統提示(system prompt):替 agent 設定角色、規則與行事風格——這屬於第一篇講的「scaffold(行為設定層)」。
  • 脈絡管理(context management):那本「不斷加頁的筆記本」不能無限長(模型的記憶窗有上限)。真實 harness 會壓縮、摘要、或把舊內容寫進外部記憶(Memory),這套功夫叫 context engineering,和單純寫提示詞是兩回事。
  • 平行工具呼叫:模型一次要求用三個工具時,同時執行而不是排隊,省時間。
  • 人機協作(human-in-the-loop):遇到「刪除檔案」「送出付款」這類危險動作時,先暫停、問過人類再執行。
  • 可觀測性(observability):把每一圈的決策、工具呼叫、花費記錄下來,方便除錯與算成本。

好消息是:這些你不一定要自己重刻。像 Claude Agent SDK 就把「agent 迴圈+內建工具(讀檔、跑指令、搜尋網路)+脈絡管理」整套打包好,讓你直接站在巨人肩膀上。但正因為你已經親手拆過這個迴圈,你會清楚知道那些工具底下到底發生了什麼——這就是這篇實作篇的價值。

新手最常踩的 5 個坑

最後幫你把雷點整理成一張清單,照著檢查能省下大量除錯時間:

  • ① 忘了把模型的 tool_use 回應也存進 messages。只存工具結果、漏存模型的請求,對話歷史就斷了——這是最常見的 bug。
  • ② tool_use_id 對不上。回填結果時用錯 id,模型會把結果配到錯的問題上。
  • ③ 用 while True 沒設上限。模型一鬼打牆就無限燒錢,務必設最大迴圈數。
  • ④ 工具出錯就讓程式崩潰。應該用 is_error 回填給模型,給它自我修正的機會。
  • ⑤ 工具 description 寫得太隨便。說明書含糊,模型就選錯工具、給錯參數。把它當成「交辦給新同事的便條」來寫。

結語:你已經懂得比 99% 的人多了

恭喜你——讀完這兩篇,你已經從零理解、並親手拆解了一個 AI Agent 的核心。下次再聽到「AI Agent」「自動化工作流」「這個 agent 會自己跑」,你腦中浮現的不再是黑盒子,而是那個清清楚楚的 while 迴圈:問模型 → 執行工具 → 回填結果 → 再問,直到完成。

這就是整個 AI Agent 浪潮的引擎室。看懂它,你就有能力判斷一個 agent 產品好不好、自己的任務該用 workflow 還是 agent、以及——當你準備好時——動手打造屬於自己的那一個。

👉 想繼續往下走?延伸閱讀:

免責聲明:本文為教育與觀念示範目的,文中偽代碼為「易讀優先」的教學寫法,省略了真實程式所需的型別、錯誤邊界與 API 細節,不可直接複製當作正式程式碼使用;實作請以各家官方 SDK 文件為準。「harness」「scaffold」「agent」等術語在 AI 領域仍在演變,本文採用業界較通行的說法。AI 系統具有輸出錯誤資訊的可能,重要決策請由人類複核後執行。資料來源包含 Claude 官方「打造工具使用 agent」文件、Anthropic 《Building Effective Agents》《Writing Tools for Agents》Claude Agent SDK 文件HuggingFace Agent 詞彙表

ALPHALAB 社群

有問題?來 Telegram 聊

和 Terry、編輯、其他網友一起討論這篇文章。提問、分享觀點,回覆更即時。

加入 Telegram 討論

📩 訂閱 AlphaLab 電子報

每週一封,第一時間收到新文章與投資觀察。

我們不會 spam,隨時可退訂。