The Agent Loop
Core LoopMinimal Closed Loop|130 LOC|1 tools
An agent is just a loop: send messages, execute tools, feed results back, repeat.
s00 > [ s01 ] > s02 > s03 > s04 > s05 > s06 > s07 > s08 > s09 > s10 > s11 > s12 > s13 > s14 > s15 > s16 > s17 > s18 > s19
沒有迴圈,就沒有 agent。
這一章先教你做出一個最小但正確的迴圈,再告訴你為什麼後面還需要更完整的控制平面。
這一章要解決什麼問題
語言模型本身只會“生成下一段內容”。
它不會自己:
- 開啟檔案
- 執行命令
- 觀察報錯
- 把工具結果再接著用於下一步推理
如果沒有一層程式碼在中間反覆做這件事:
發請求給模型
-> 發現模型想調工具
-> 真的去執行工具
-> 把結果再喂回模型
-> 繼續下一輪
那模型就只是一個“會說話的程式”,還不是一個“會幹活的 agent”。
所以這一章的核心目標只有一個:
把“模型 + 工具”連線成一個能持續推進任務的主迴圈。
先解釋幾個名詞
什麼是 loop
loop 就是迴圈。
這裡的意思不是“程式死迴圈”,而是:
只要任務還沒做完,系統就繼續重複同一套步驟。
什麼是 turn
turn 可以理解成“一輪”。
最小版本里,一輪通常包含:
- 把當前訊息發給模型
- 讀取模型回覆
- 如果模型呼叫了工具,就執行工具
- 把工具結果寫回訊息歷史
然後才進入下一輪。
什麼是 tool_result
tool_result 就是工具執行結果。
它不是隨便列印在終端上的日誌,而是:
要重新寫回對話歷史、讓模型下一輪真的能看見的結果塊。
什麼是 state
state 是“當前執行狀態”。
第一次看到這個詞時,你可以先把它理解成:
主迴圈繼續往下走時,需要一直帶著走的那份資料。
最小版本里,最重要的狀態就是:
messages- 當前是第幾輪
- 這一輪結束後為什麼還要繼續
最小心智模型
先把整個 agent 想成下面這條迴路:
user message
|
v
LLM
|
+-- 普通回答 ----------> 結束
|
+-- tool_use ----------> 執行工具
|
v
tool_result
|
v
寫回 messages
|
v
下一輪繼續
這條圖裡最關鍵的,不是“有一個 while True”。
真正關鍵的是這句:
工具結果必須重新進入訊息歷史,成為下一輪推理的輸入。
如果少了這一步,模型就無法基於真實觀察繼續工作。
關鍵資料結構
1. Message
最小教學版裡,可以先把訊息理解成:
{"role": "user", "content": "..."}
{"role": "assistant", "content": [...]}
這裡最重要的不是欄位名字,而是你要記住:
訊息歷史不是聊天記錄展示層,而是模型下一輪要讀的工作上下文。
2. Tool Result Block
當工具執行完後,你要把它包裝回訊息流:
{
"type": "tool_result",
"tool_use_id": "...",
"content": "...",
}
tool_use_id 的作用很簡單:
告訴模型“這條結果對應的是你剛才哪一次工具呼叫”。
3. LoopState
這章建議你不要只用一堆零散區域性變數。
最小也應該顯式收攏出一個迴圈狀態:
state = {
"messages": [...],
"turn_count": 1,
"transition_reason": None,
}
這裡的 transition_reason 先只需要理解成:
這一輪結束後,為什麼要繼續下一輪。
最小教學版只用一種原因就夠了:
"tool_result"
也就是:
因為剛執行完工具,所以要繼續。
後面到了控制面更完整的章節裡,你會看到它逐漸長成更多種原因。
如果你想先看完整一點的形狀,可以配合讀:
最小實現
第一步:準備初始訊息
使用者的請求先進入 messages:
messages = [{"role": "user", "content": query}]
第二步:呼叫模型
把訊息歷史、system prompt 和工具定義一起發給模型:
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=messages,
tools=TOOLS,
max_tokens=8000,
)
第三步:追加 assistant 回覆
messages.append({"role": "assistant", "content": response.content})
這一步非常重要。
很多初學者會只關心“最後有沒有答案”,忽略把 assistant 回覆本身寫回歷史。
這樣一來,下一輪上下文就會斷掉。
第四步:如果模型呼叫了工具,就執行
results = []
for block in response.content:
if block.type == "tool_use":
output = run_bash(block.input["command"])
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
第五步:把工具結果作為新訊息寫回去
messages.append({"role": "user", "content": results})
然後下一輪重新發給模型。
組合成一個完整迴圈
def agent_loop(state):
while True:
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=state["messages"],
tools=TOOLS,
max_tokens=8000,
)
state["messages"].append({
"role": "assistant",
"content": response.content,
})
if response.stop_reason != "tool_use":
state["transition_reason"] = None
return
results = []
for block in response.content:
if block.type == "tool_use":
output = run_tool(block)
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
state["messages"].append({"role": "user", "content": results})
state["turn_count"] += 1
state["transition_reason"] = "tool_result"
這就是最小 agent loop。
它如何接進整個系統
從現在開始,後面所有章節本質上都在做同一件事:
往這個迴圈裡增加新的狀態、新的分支判斷和新的執行能力。
例如:
s02往裡面接工具路由s03往裡面接規劃狀態s06往裡面接上下文壓縮s07往裡面接許可權判斷s11往裡面接錯誤恢復
所以請把這一章牢牢記成一句話:
agent 的核心不是“模型很聰明”,而是“系統持續把現實結果喂回模型”。
為什麼教學版先接受 stop_reason == "tool_use" 這個簡化
這一章裡,我們先用:
if response.stop_reason != "tool_use":
return
這完全合理。
因為初學者在第一章真正要學會的,不是所有複雜邊界,而是:
- assistant 回覆要寫回歷史
- tool_result 要寫回歷史
- 主迴圈要持續推進
但你也要知道,這只是第一層簡化。
更完整的系統不會只依賴 stop_reason,還會自己維護更明確的續行狀態。
這是後面要補的,不是這一章一開始就要背下來的東西。
初學者最容易犯的錯
1. 把工具結果打印出來,但不寫回 messages
這樣模型下一輪根本看不到真實執行結果。
2. 只儲存使用者訊息,不儲存 assistant 訊息
這樣上下文會斷層,模型會越來越不像“接著剛才做”。
3. 不給工具結果繫結 tool_use_id
模型會分不清哪條結果對應哪次呼叫。
4. 一上來就把流式、併發、恢復、壓縮全塞進第一章
這會讓主線變得非常難學。
第一章最重要的是先把最小回路搭起來。
5. 以為 messages 只是聊天展示
不是。
在 agent 裡,messages 更像“下一輪工作輸入”。
教學邊界
這一章只需要先講透一件事:
Agent 之所以從“會說”變成“會做”,是因為模型輸出能走到工具,工具結果又能回到下一輪模型輸入。
所以教學倉庫在這裡要刻意停住:
- 不要一開始就拉進 streaming、retry、budget、recovery
- 不要一開始就混入許可權、Hook、任務系統
- 不要把第一章寫成整套系統所有後續機制的總圖
如果讀者已經能憑記憶寫出 messages -> model -> tool_result -> next turn 這條迴路,這一章就已經達標了。
一句話記住
Agent Loop 的本質,是把“模型的動作意圖”變成“真實執行結果”,再把結果送回模型繼續推理。