iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Claude AI

從 LLM 到 Agent:用 Claude 拆解現代 AI 工程的每一層系列 第 22 篇

代理工程的三件事:讓迴圈停得對、會反思、會規劃

  • 分享至 

  • xImage
  •  

第 21 篇那個 while 停得很乾淨,因為模型自己走到 end_turn,說「做完了」。但這件事是模型說了算。我把預算上限設成一分錢(--max-budget-usd 0.01),同一個數行數的任務,它開了一張工具單就被截斷,total.txt 根本沒寫出來。模型還沒說做完,是外面有人先喊了停。

先講結論:第 21 篇做的是 loop engineering,把迴圈的樣子看清楚。這一篇進到 agent engineering。LangChain 團隊把它定義成「把不確定的 LLM 系統,反覆打磨成可靠的產品」,是一個 build、test、ship、observe、refine 的循環(2025-12)。這一篇抓其中「讓它可靠」的那一段:讓這個迴圈停得下來、停得對,順便讓它每一圈都走得聰明一點。拆成三件事:外部的硬煞車(輪數、詞元、預算、時間)、錯誤路徑(一步錯了別讓它無限重試),以及反思與規劃(讓它少繞幾圈、也更知道何時算完)。


30 秒實驗

乾淨的資料夾,三個 .py 檔,還沒有 total.txt。同樣一句「數行數、加總、寫進 total.txt」,這次加一個預算上限(2026-10-06,Claude Code):

claude -p "……寫進 total.txt……" --max-budget-usd 0.01
→ {"subtype": "error_max_budget_usd", "is_error": true,
   "num_turns": 2, "total_cost_usd": 0.1175}
→ total.txt:沒有寫出來,任務沒做完

它開了一張 Bash 的單就停了。(只跑一次,是例子不是證據。)兩個地方值得看:

  • 這是外部喊停。 模型沒有走到 end_turn,是 Claude Code 在超過預算後把整個迴圈收掉,退出碼是 1(錯誤),不是 0。
  • 上限 0.01,實際花了 0.1175,超了十倍。 因為它是每一輪之後才檢查,不是花到 0.01 就當場剎車。一輪本身就比上限貴時,必然會超出。煞車停在「兩輪之間」,不是「一輪之中」。

兩種停法

迴圈停下來有兩個來源。內部是模型自己回 end_turn,第 21 篇那次就是。但這靠模型判斷它做完了,你沒得保證,它也可能判斷錯。

所以要有外部的硬煞車,也就是「你說夠了」。官方 tool-use 文件列的那幾個跳出條件,除了 end_turn,還有 max_tokens(這一則寫到上限)、stop_sequence、refusal,碰到任何一個,迴圈都該跳出(2026-10-06 查)。另外還有幾種:

  • 預算:--max-budget-usd,上面實驗用的那個,花超過就停。
  • server 端迴圈的次數上限:工具在 Anthropic 那邊自己跑一圈(像網頁搜尋)時有迭代上限,撞到上限會回 pause_turn 而不是 end_turn,提醒你這一輪還沒做完。
  • 單一工具的 timeout:第 18 篇那個卡住八分鐘的 MCP 工具,靠的就是 per-server 的 timeout 把它拉回來。

寫成實際可跑的程式,這些煞車就是加在那個迴圈上的幾行(第 21 篇給的是虛擬碼,這裡用 Anthropic SDK 補上,tools 與 run_tool 的結構就是第 16 篇那種工具定義與分派):

MAX_TURNS, MAX_RETRIES, BUDGET_USD = 20, 2, 0.50
cost, retries = 0.0, 0

for turn in range(MAX_TURNS):                 # 煞車一:輪數上限
    reply = client.messages.create(model="claude-opus-5-5",
        max_tokens=1024, tools=tools, messages=messages)
    cost += usd(reply.usage)                  # 把這一輪的 usage 換算成錢
    messages.append({"role": "assistant", "content": reply.content})
    if cost > BUDGET_USD:                     # 煞車二:預算
        break
    if reply.stop_reason != "tool_use":       # 模型自己走到 end_turn
        break
    results = []
    for block in reply.content:
        if block.type == "tool_use":
            try:
                out = run_tool(block.name, block.input)
            except Exception as e:            # 煞車三:工具出錯
                retries += 1
                out = f"error: {e}"           # 把錯誤講清楚,接回去讓它改
            results.append({"type": "tool_result",
                            "tool_use_id": block.id, "content": out})
    if retries > MAX_RETRIES:                 # 一直錯就停,別用預算去燒
        break
    messages.append({"role": "user", "content": results})

while True 換成 for turn in range(MAX_TURNS),就是最簡單的一道硬煞車。其餘兩道(預算、重試)是迴圈裡多出來的幾個 if。

煞車不是越緊越好。我那次一分錢的上限,把一個本來會成功的 run 砍在半路。設上限,是在「別讓它失控」和「別砍掉好結果」之間抓一條線。


一步錯了,它該怎麼走

第 21 篇那份軌跡裡,第一張單被權限擋下,模型下一輪換個寫法就過了,這是錯誤路徑運作得好的樣子:失敗的結果接回迴圈,模型看到了、修正了。

但同一個迴圈也可能壞在這裡。要是那個錯誤一直回去,模型可能一招換過一招試不停,繞在同一個環上出不來(這正是第 21 篇講的,那張圖裡的循環,有時候就是繞不出去)。所以錯誤路徑要有人管:限制重試次數、把錯誤訊息講清楚讓它修得動(第 18 篇說過,MCP 工具只有 ToolError 的訊息會進到模型眼裡,訊息寫得好不好,直接決定它改不改得對),真的卡住就直接停,別用預算去燒。上面那段的 try/except、retries 和那句 break,就是在做這三件事。


讓它少繞幾圈:反思與規劃

煞車和重試是「別讓它爛下去」。另一半是讓它一開始就走得好、少繞幾圈。這裡有兩個不改模型、只改迴圈組織方式的做法。Andrew Ng 把單一代理常見的做法歸成幾個 agentic pattern:工具使用(第 16 篇)、反思、規劃,另一個「多代理」是下一層的事。這一段講其中的反思和規劃。

反思(reflection)。 核心動作是把「產出」和「檢查」拆成兩步:先讓模型給一版,再讓它回頭批評自己這一版,然後改寫。Self-Refine(Madaan et al., NeurIPS 2023)就是同一個模型替自己的輸出產生回饋、再據此反覆改,平均把表現拉高約 20 個百分點。Reflexion(Shinn et al., NeurIPS 2023)更進一步,把反省寫成一段文字、存進情節記憶,下一輪帶著這段教訓再做,HumanEval 的 pass@1 到 91%。這一步有個隱含條件:那段教訓要有地方存、下一圈才讀得回去,不然每一圈都從頭反省,這就回到第 13 篇那一層記憶。第 21 篇那句夾在讀檔和寫檔之間的「Counts match」,就是一次沒有存下來的微型反思,先對帳再動手。

規劃(planning)。 Plan-and-Solve(Wang et al., ACL 2023)的做法是先擬一個計畫、把整個任務拆成子任務,再照計畫一步步做,而不是走一步想一步。這跟第 21 篇的 ReAct 剛好是同一條軸的兩端:ReAct 是推理和行動交錯、邊走邊想,Plan-and-Solve 是先把整條路想好再走。先規劃的迴圈繞的圈更少,也更不容易走到一半發現方向錯了,代價是它一開始就賭了一條路。

這兩個都呼應第 21 篇的迴圈工程:你沒有動模型,是在它外面那圈 while 裡多安排了「先想」和「先計畫」。


這麼做的代價

第一,反思和規劃要多花模型呼叫。 多一輪自我批評、多一段計畫,就是多送一次脈絡、多一筆詞元,更慢也更貴。省下的圈數划不划得來,要看任務。

第二,煞車設太緊會砍掉好結果。 一分錢的上限把成功的 run 攔腰截斷就是例子。太鬆它會失控,太緊它做不完,這條線得按任務調。

第三,規劃錯了會把整條路帶偏。 先定死的計畫,如果一開始就想錯方向,後面每一圈都在錯的路上走。規劃省圈數,前提是規劃本身不離譜。


這一篇多了什麼,又多付了什麼

多了什麼能力:你知道一個代理迴圈有哪些停法(模型自己的 end_turn,加上輪數、詞元、預算、timeout 這些外部硬煞車),知道錯誤路徑要限制重試、把錯誤講清楚,也知道反思與規劃這兩個 pattern 能讓它少繞幾圈。

多付了什麼代價:反思規劃多出來的詞元、一條鬆緊都不對的煞車線,以及一個可能從頭就錯的計畫。


下一篇

這些煞車、重試、反思,自己裸寫一個 while 都得一條一條兜。可是你平常用 Claude Code,根本沒寫這些,它就幫你做了。那個夾在你和模型中間的「執行框架」,到底還偷偷替你做了多少事?

下一篇拆 harness:裸迴圈之外,它補上的那一層。


延伸閱讀


上一篇
代理說穿了就是一個 while:拆一份 Claude 的執行軌跡
系列文
從 LLM 到 Agent:用 Claude 拆解現代 AI 工程的每一層 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言