Lesson 01 · 基礎

沒有迴圈,就沒有 agent

Claude Code 的整個秘密可以寫成一行:while stop_reason == "tool_use"

⏱ 約 10 分鐘 · 📝 3 個可互動元件 · 🧑‍💻 基於 shareAI-lab · s01_agent_loop.py

一個 agent 到底在做什麼?

當你在終端機裡跑 Claude Code、讓它「把所有 TODO 注釋整理成一份清單」,你看到的是:它自己決定先 grep、再 cat 幾個檔案、然後輸出 Markdown。模型自己不會執行程式碼——它只會請求執行。真正讓它看起來「有手有腳」的,是它外面那一圈 30 行左右的黏合程式碼。

這一課就是要把那圈黏合程式碼拆開看清楚。它叫 agent loop(智能體迴圈),結構就是:

while response.stop_reason == "tool_use":
    response = LLM(messages, tools)      # 1. ask the model
    execute_tools(response.tool_calls)   # 2. run what it asked for
    messages.append(tool_results)        # 3. feed results back

就這樣。沒了。production 的 Claude Code 在這上面疊了權限、hook、子 agent、worktree 隔離、記憶壓縮——但核心還是這四行。

關鍵直覺:模型每次回覆要麼「我想調工具」,要麼「我說完了」。只要還在前一種狀態,迴圈就繼續;一旦進入後一種狀態,迴圈就退出。判斷依據是回應裡的 stop_reason 欄位。

看 messages[] 一步步長出來

下面這個迴圈模擬的任務是:「目前目錄有哪些檔案?然後讀一下 package.json」。點 Step,每按一次前進一個迴圈內的動作。左邊是給人看的對話氣泡,右邊是真正塞給模型的 messages[] 陣列——注意看它如何增長。

工具結果必須重新進入訊息歷史

新手最容易踩的坑,是把「執行工具」當成一個副作用——執行了就完事了。但模型下一輪推理只能看到 messages[]不在裡面的東西它就不知道發生過。少 append 一步,整個迴圈就錯了。

錯法不是都一樣的。常見兩種:

  • 忘記 append:模型下一輪看不到結果,它會再問一次同一個工具——於是你得到一個無限迴圈。
  • append 了但丟掉 tool_use_id:Anthropic API 會直接報錯 tool_result must have tool_use_id,迴圈崩在 API 呼叫那一步。

同一個任務:「統計專案裡有多少個 Python 檔案」。兩個版本並排跑,看結局差別。

讀懂 stop_reason

模型每次回傳都會帶一個 stop_reason。決定要不要繼續迴圈,就看這個欄位:

  • tool_use — 模型想調工具,繼續迴圈
  • end_turn — 模型覺得說完了,退出迴圈
  • max_tokens — 生成到了 token 上限被截斷,退出迴圈(一般當例外處理,提示使用者輸出不完整)。
  • stop_sequence — 碰到自訂停止字串,退出迴圈

寫錯成「只要沒 tool_use 就繼續」,或者忽略 max_tokens 直接當作正常 end,都是常見 bug。

動手跑一下

本地克隆 shareAI-lab/learn-claude-code

git clone https://github.com/shareAI-lab/learn-claude-code
cd learn-claude-code
pip install -r requirements.txt
cp .env.example .env  # fill in ANTHROPIC_API_KEY
python agents/s01_agent_loop.py

然後讓它做點真事:幫我數一下這個倉庫裡有多少 .py 檔案。你會看到它發一條 ls -R、看到輸出、再發一條 find . -name "*.py" | wc -l、然後告訴你答案。整個過程就是這個 loop 在迴圈。

Interactive

Widget 1 · Loop Stepper · messages[] 生長

看懂:工具結果是以特殊格式(tool_result 塊,帶 tool_use_id)被塞回 messages 的;這是模型下一輪推理時唯一知道「工具執行完了、結果是 X」的途徑。

對話視圖未開始
點 Step 開始 →
messages[] JSONlength: 0
[]
stop_reason:
Interactive

Widget 2 · Break the Loop · 正確 vs 忘記 append

看懂:只要漏一行 messages.append(tool_results),整個 agent 就不再是 agent——它變成一台每輪忘記前一輪的點讀機。

while True:
    resp = LLM(msgs, tools)
    msgs.append({"role":"assistant",
                 "content": resp.content})
    if resp.stop_reason != "tool_use":
        return
    results = execute(resp.tool_uses)
    msgs.append({"role":"user",
                 "content": results})
模擬執行
while True:
    resp = LLM(msgs, tools)
    msgs.append({"role":"assistant",
                 "content": resp.content})
    if resp.stop_reason != "tool_use":
        return
    results = execute(resp.tool_uses)
    # BUG: forgot to append results
    # msgs.append(...)
模擬執行
Interactive

Widget 3 · Spot the stop_reason · 判斷 4 個真實回應

每個都是真實可能的模型回應片段。點「繼續」還是「退出」,即時看對錯。

答對 0 / 4