昨天我們替 Memora 加入第一個 Tool:
count_english_words
模型可以判斷是否需要精確計算英文單字數量,再由 Application 執行 Python Function,最後把 Tool Result 送回模型。
現在很容易出現一個問題:
Memora 已經會使用 Tool,所以它現在就是 Agent 了嗎?
如果只看功能,可能會想直接回答「是」。但從程式控制流程來看,目前更精確的說法是:
Day 25 的 Memora 是一個具備 Memory 的 Tool-using Chatbot,而且 Tool 執行被包在固定 Workflow 裡;它已經有 Agent 的部分元素,但還沒有 Agent Loop。
今天不另外建立一套程式,也不提前完成 Day 27。這一篇會直接拆開昨天的 run_model_with_tools(),確認現在到底是誰在決定下一步。
「Agent」在不同文章、產品或 Framework 裡,範圍可能不完全相同。如果只用名稱判斷,很容易變成:
有 System Prompt
→ Agent
有 Memory
→ Agent
有 Tool
→ Agent
這些說法都太寬。
為了讓後面的程式可以驗證,這個系列採用一個操作型定義:
Agent 是一個能根據目前目標與 Observation,在受限制的執行環境中反覆選擇下一個 Action,直到符合 Stop Condition 的系統。
核心不是名稱,也不是模型是否看起來很聰明,而是執行期間是否真的存在:
Goal
→ Decide
→ Action
→ Observation
→ Decide Again
→ Stop
OpenAI 的 Agent 文件也把 Agent Loop 描述為:呼叫模型、檢查輸出、執行 Tool Call、繼續執行,直到模型產生不再需要 Tool 的最終答案。
先用同一組標準比較:
| 類型 | 誰決定主要步驟 | 能否使用 Tool | 能否根據結果再次決策 | 如何停止 |
|---|---|---|---|---|
| Chatbot | Application 呼叫一次模型 | 不一定 | 通常不能 | 模型回傳回答 |
| Tool-using Chatbot | 模型可選擇 Tool,但流程通常有限 | 可以 | 視實作而定 | 多半由固定流程結束 |
| Workflow | Python 或 Graph 預先決定路徑 | 可以 | 只走預先寫好的 Branch | 程式規則 |
| Agent | 模型依 Goal 與 Observation 選擇下一步 | 通常可以 | 可以反覆決策 | Final Answer、限制或中止條件 |
這四個名稱不是互斥的產品分類。一個實際系統可以同時是:
對外看起來是 Chatbot
內部使用固定 Workflow
其中某一段包含 Agent Loop
真正需要分辨的是每一段流程的 Control Flow 由誰掌握。
Memora 在 Day 25 以前已經有:
Conversation History
Summary
User Profile
Long-term Memory
Semantic Retrieval
Memory Policy
Importance、Recency 與 Reconciliation
但這些能力不會自動把 Chatbot 變成 Agent。
Memory 回答的是:
系統能保留什麼狀態?
這一輪應該取回哪些過去資訊?
Agent Control Flow 回答的是:
為了完成目前目標,下一個動作是什麼?
看到執行結果後,是否還需要其他動作?
什麼時候應該停止?
因此可能存在:
有 Long-term Memory、但沒有 Agent Loop 的 Chatbot
沒有 Long-term Memory、但能完成多步任務的 Agent
同時具有 Memory 與 Agent Loop 的 Agent
這個系列前 24 天先建立 Memory,是為了讓 Day 28 的 Agent 可以操作一套已經清楚分層的 Memory System,而不是因為「有 Memory」就已經等於 Agent。
昨天的 run_model_with_tools() 可以先縮成以下結構:
response = client.responses.create(
tools=TOOLS,
tool_choice="auto",
parallel_tool_calls=False,
# 其他參數保留
)
function_calls = [
item
for item in response.output
if item.type == "function_call"
]
if not function_calls:
return response.output_text
tool_call = function_calls[0]
tool_output = execute_tool_call(
tool_call
)
final_response = client.responses.create(
tools=TOOLS,
tool_choice="none",
parallel_tool_calls=False,
# response.output 與 tool_output
# 已加入新的 Input
)
return final_response.output_text
第一個 Request 中:
tool_choice="auto"
所以模型可以選擇直接回答,或要求一次 Tool Call。這部分已經具有有限的決策能力。
但是第二個 Request 使用:
tool_choice="none"
表示模型看完 Tool Result 後,只能整理 Final Response,不能再選擇另一個 Action。
因此整條路徑已經被 Application 寫死:
最多一次 Tool Call
→ 一定進入 Final Response
→ Return
這是一個有條件分支的 Workflow,還不是可以反覆決策的 Agent Loop。
雖然還不是完整 Agent Loop,昨天其實已經完成兩個重要元件。
模型回傳:
function_call
name = count_english_words
arguments = {...}
這代表模型選擇了一個可執行動作。
Application 執行 Function 後回傳:
function_call_output
call_id = ...
output = {...}
這是 Action 的執行結果,也就是下一次模型判斷可以觀察到的資料。
目前真正缺少的是:
Observation
→ 再次決定 Action 或 Final Answer
Day 25 的模型雖然看得到 Observation,但 tool_choice="none" 已經替它決定:下一步只能回答。
假設使用者要求:
請比較下面兩句話的英文單字數量,告訴我哪一句比較長:
A: I study English every day.
B: I have studied English for three years.
目前的 Tool 一次只接受:
{
"text": "一段英文"
}
合理的處理步驟可能是:
Action 1
count_english_words(A)
Observation 1
A = 5
Action 2
count_english_words(B)
Observation 2
B = 7
Final Answer
B 比 A 多 2 個單字
Day 25 執行完 Action 1 後就強制進入 Final Response,因此模型不能再呼叫同一個 Tool 計算B。
這個例子不需要新增第二個 Tool,就能看出:
Agent Loop 的關鍵不是 Tool 數量,而是模型能否根據 Observation 繼續選擇下一個步驟。
假設我們直接在 Python 寫:
count_a = count_english_words(
sentence_a
)
count_b = count_english_words(
sentence_b
)
result = compare_counts(
count_a,
count_b
)
這可以正確完成任務,但執行順序完全由程式預先決定,因此它是 Workflow。
Agent Loop 則會讓模型在每一步判斷:
目前已有什麼資料?
還缺少什麼?
下一個 Tool Call 是什麼?
資訊是否已經足以回答?
兩者沒有誰一定比較好。
當步驟明確、規則穩定時,Workflow 通常更容易測試、成本更低,也更可預測。只有當任務路徑會依中間結果變化時,才需要增加 Agentic Control。因此「能寫成 Agent」不是採用 Agent 的充分理由。
在這個系列裡,一個最小 Agent Run 至少包含:
| 元件 | 在 Memora 中的對應內容 |
|---|---|
| Goal | Current User Message |
| Instructions | SYSTEM_PROMPT |
| State | input_messages 與累積的 Output Items |
| Actions | TOOLS 中允許的 Function |
| Executor | execute_tool_call() |
| Observation | function_call_output |
| Decision Maker | LLM |
| Loop | 重複 Model → Tool → Model |
| Stop Condition | Final Answer、Step Limit 或錯誤中止 |
Day 25 已經有其中大部分元件,卻缺少通用的 Loop 與 Stop Control。
這也是為什麼明天不需要推翻整個程式。Day 27 的主要修改會集中在:
run_model_with_tools()
其餘 Memory、Tool Definition 與 Dispatcher 都可以繼續沿用。
Agent 並不是讓模型取得所有權限,再自行處理一切。Application 仍然要決定:
提供哪些 Tools
每個 Tool 可以接受哪些 Arguments
哪些動作需要人工確認
一次 Run 最多可以執行幾步
Tool Output 可以有多大
發生錯誤時如何處理
哪些資料不能送給模型
模型的自主性只存在於這些邊界之內:
Application 定義 Action Space
LLM 在 Action Space 中選擇下一步
Day 25 的 TOOL_HANDLERS Allowlist、Argument Validation 與長度限制都要保留。加入 Agent Loop 之後,它們反而更重要,因為同一個 Run 可能執行多次 Tool Call。
如果只有:
while True:
卻沒有明確停止條件,Agent 可能反覆呼叫 Tool,持續消耗 Token,甚至重複執行具有副作用的動作。
Day 27 至少要處理三種停止結果:
正常停止
模型沒有再要求 Tool,已產生 Final Answer
限制停止
達到 MAX_AGENT_STEPS
錯誤停止
API、Arguments 或 Tool Execution 發生無法繼續的錯誤
未來如果 Tool 會修改資料,還可能需要:
Approval Pause
也就是先請使用者確認,再執行具有風險或不可逆的 Action。
今天先定義這些邊界,不在 Day 26 偷跑實作 Loop。
有時候會把 Agent 流程寫成:
Thought
→ Action
→ Observation
但工程上真正需要保存與檢查的是:
模型輸出的 Tool Call
Application 實際執行的 Action
Tool 回傳的 Observation
最後的 Result 或 Stop Reason
不需要要求模型輸出隱藏的逐步推理,也不應把一段看起來像思考過程的文字當成可靠的控制訊號。
Memora 會繼續使用 Responses API 的結構化 Output Items 判斷:
function_call
function_call_output
output_text
控制流程依據的是可驗證資料,不是自由格式的「我接下來想做……」。
Day 25 使用 Application-managed State:
next_input = list(input_messages)
next_input += response.output
next_input.append(
function_call_output
)
Day 27 會繼續使用同一種策略,把每一輪新的:
response.output
function_call_output
加入同一個 Working Input。
不會在中途突然改用:
previous_response_id
因為同時重播完整 Local Input,又使用 Server-managed Continuation,可能讓相同 Context 被重複帶入。
OpenAI 的 Agent 文件也建議一段 Conversation 選擇一種主要 State Strategy。Memora 目前重視可觀察與可控制,所以繼續由 Application 管理這次 Agent Run 的 State。
現在逐項檢查:
| 能力 | Day 25 是否具備 |
|---|---|
| 多輪 Conversation State | 有 |
| Long-term Memory | 有 |
| Memory Retrieval | 有 |
| Tool Definition | 有 |
| 模型選擇是否使用 Tool | 有 |
| Application 執行 Tool | 有 |
| Tool Result 回到模型 | 有 |
| 根據 Observation 再選 Action | 沒有 |
| 通用 Agent Loop | 沒有 |
| Step Limit | 沒有 |
| 動態 Stop 判斷 | 沒有 |
所以目前最精確的定位是:
一個有狀態、有長期記憶,並能在固定流程中使用一次 Tool 的 Tool-using Chatbot。
它距離 Agent 已經不遠,但這個差距不是再加一個更長的 Prompt,而是補上 Runtime Control Loop。
明天會完整保留:
count_english_words()
TOOLS
TOOL_HANDLERS
execute_tool_call()
SYSTEM_PROMPT
Short-term Memory
User Profile
Long-term Memory Retrieval
Day 24 的 Memory Write Path
主要只修改 Day 25 的:
run_model_with_tools()
目前是:
第一次 Model Call
→ 最多一次 Tool Call
→ 強制 Final Response
Day 27 會變成:
在 Step Limit 內重複:
Model Call
→ 有 Tool Call:執行並加入 Observation
→ 沒有 Tool Call:回傳 Final Answer
這樣才是從昨天的程式自然演進,而不是突然改用另一套 Agent Framework。
今天沒有增加新的 Tool,也沒有改動 Day 24 的 Memory System。這是刻意保留的一個 Architecture Checkpoint:先確認目前的控制流程,再開始增加自主性。
四個概念可以簡化成:
Chatbot
→ 主要目標是產生回答
Tool-using Chatbot
→ 可以要求 Application 執行 Function
Workflow
→ 程式預先決定執行路徑
Agent
→ 模型能根據 Observation 反覆選擇下一個 Action,
直到符合 Stop Condition
昨天的 Memora 已經有:
Goal
Action
Executor
Observation
但仍然缺少:
Observation 後再次 Decision
可重複的 Loop
明確的 Step Limit
通用 Stop Condition
今天最重要的觀念是:
會使用 Tool 不等於已經是 Agent。真正的分界在於:模型能不能根據執行結果持續決定下一步,而不是只走完 Application 預先寫好的單次路徑。
下一篇會直接修改 Day 25 的 run_model_with_tools():移除第二次 Request 的強制 tool_choice="none",加入有限次數的 Loop,讓 Memora 可以反覆執行:
Decision
→ Action
→ Observation
→ Decision
同時實作 MAX_AGENT_STEPS、Final Answer 判斷、Token 累積與錯誤停止,完成第一個可控的 Agent Loop。