「把它做成 Agent」在需求討論裡出現的頻率,大概只輸「加個 AI」。但這個系列走到 Day 15,chat、streaming、多輪對話、prompt 管理、token 預算、帶 ACL 的 RAG,一路做下來沒有用到任何一個 Agent,而且沒有任何一處是將就出來的。這篇要把「什麼時候需要 Agent」變成一個答得出來的問題:讀完你會拿到一個判準(每一步走哪條邊,是程式規則決定,還是模型決定)、一張誠實的代價清單,還有下次提案前該先回答的五題。
Part 4 從這裡開始。在寫任何 Agent Framework 程式碼之前(那是 Day 17 的事),先把「要不要」想清楚——這天的 milestone 也刻意選了最不炫的形式:一份決策指南文件,不是程式碼。
最常見的想像是把三者排成能力等級:chat API 是入門、RAG 是進階、Agent 是完全體,系統夠成熟就該「升級」。這個排序錯在把差別當成任務難度。差別只有一個:控制流的所有權。
到 Day 15 為止,這個 backend 的每一條路都是程式擁有控制流。模型產生的是內容,「接下來做什麼」永遠由程式碼決定:
# code-owned:程式決定每一次模型呼叫
hits = await retriever.search(question, principal=principal)
if not hits:
return RagAnswer(status="no_answer", hits=()) # 一次模型呼叫都沒發生
answer = await generate(build_prompt(question, hits))
return RagAnswer(status="answered", answer=answer, hits=hits)
這是 Day 14 /rag 的骨架(簡化過,完整版在 services/rag.py):先檢索、零命中就直接回 no_answer、有命中才呼叫一次模型。整張流程圖在 request 到來之前就固定了,模型被關在其中一個節點裡。
Agent 翻轉的是這件事,換成模型的輸出決定下一步:
# model-owned:模型的輸出決定下一步
while not done and steps < STEP_LIMIT:
action = await llm.decide(context) # 模型選 tool、選參數、選要不要繼續
result = await tools[action.name](**action.args)
context.append(result)
steps += 1
呼叫哪個 tool、帶什麼參數、要跑幾輪、什麼時候停,全部是推理時逐 request 決定的。程式從「決定流程的人」退位成「提供工具跟上限的人」。這是所有權的讓渡,不是功能的升級。讓渡出去的東西每一項都有帳要付,後面逐項算。
把差別定義成控制流所有權之後,「什麼時候需要 Agent」就有了一個可測試的判準:
執行中的每一步要走哪條邊,能不能由程式規則決定,而不需要模型選 action?
能,就寫 code 走這張圖——不論流程是一直線(retrieval → generation)、有分支(if 的條件由程式在算)、還是 DAG。模型可以出現在任何節點裡產生內容,但節點之間怎麼走由程式決定。不能,也就是哪一步呼叫哪個 tool、帶什麼參數、什麼時候停,得看模型在中間產出什麼,才輪到 Agent。
有個常見的誤判要先拆掉:「畫不畫得成圖」不是判準。Agent loop 也畫得成一張固定的 cyclic graph——候選 tools、model → tool → model 的邊、iteration limit 都能事先畫出來,上一節的 while 迴圈就是這張圖。真正動態的不是拓撲,是 runtime 選邊的人。固定圖仍然好用,但它是輔助的 heuristic:一張沒有「模型選邊」節點的圖,才等於可以全部寫成 code。
這不是我的一家之言。Microsoft Agent Framework 的官方文件把這句話寫得比誰都直白(Agent Framework overview,查核 2026-08):
If you can write a function to handle the task, do that instead of using an AI agent.
寫得出 function 就寫 function,別用 Agent。
同一份文件甚至把「多步驟但流程明確」的場景指去它的 workflows(graph-based、程式控制執行順序),而不是 agents。連賣 Agent 框架的人,都先勸你想清楚要不要用。
Azure Architecture Center 則把它放上一條複雜度光譜:直接呼叫模型 → 單一 agent 加 tools → multi-agent orchestration(AI agent orchestration patterns,查核 2026-08)。
它的要求同樣直白:「use the lowest level of complexity that reliably meets your requirements」——用可靠滿足需求的最低複雜度。
注意光譜的第一格:如果一次模型呼叫加上好的 prompt 就能解決,連 agent 都不需要。這正是本系列 Day 5 到 Day 10 一直在做的事。
把兩個來源疊起來,整個決策就是一張圖:

順帶示範了一件事:這張圖裡每一步走哪條邊都由固定規則決定、沒有一個節點要模型選 action——所以它是一份 checklist,不是一個 agent。
「用 RAG」其實有兩種做法,而本系列在 Day 14 否決過其中一種。被否決的叫 agentic RAG:把檢索做成一個 tool 交給模型,讓它自己決定要不要查、查幾次、什麼時候夠了。聽起來更聰明,而我們選了寫死的 retrieval → generation 管線。選它的理由很實際:下面四個保證的證明位置,都被管線形式固定下來了;換成 agentic loop,四項都得逐項重新推導——保得住的要換地方證明,保不住的就降級。
| 已出貨的保證 | 為什麼需要 code-owned 控制流 |
|---|---|
結構性 no-answer(Day 14):零命中 → no_answer,零次 LLM 呼叫 |
只有「檢索一定執行、LLM 呼叫寫在 if 之後」才成立。模型自主的版本可能跳過檢索、直接用參數記憶回答,「它到底查了沒」變成逐 request 的推理結果 |
ACL 貫穿(Day 15):每次檢索都帶 server-built preFilter,principal 參數沒有預設值 |
要分兩層。「每次 search 都有 filter」:只要 tool wrapper 同樣 fail-closed 地包住 SearchClient.search,這仍是型別層級保證,不因 tool 由模型選用而降級。「每個回答都先檢索、且只取材自過濾後結果」:這才是 agentic 版本失去結構證明、要改靠 eval 與 trace 維護的部分 |
Prompt 預算(Day 14):rank-ordered 選取、裝不下就停,sources 精確等於模型看到的 |
前提是組 prompt 的人是程式。Agent 自己累積 tool 結果時,context 的成長由它作主 |
| 成本形狀(Day 9):一個 turn ≈ 一次模型呼叫,預算檢查在推理之前 | Agent loop 是每 turn N + 1 次模型呼叫(N=把 tool 結果依序送回模型的 rounds,由模型在執行時決定)。iteration 與 context 上限仍能給出最壞情況的上界,但實際次數變成逐 request 的變數 |
這張表不是反 Agent 宣言。它是一張重新推導清單:哪天真的要把管線換成 loop,表裡每一行都得重新想一次「這個保證還成立嗎?不成立的話我拿什麼補?」。管線一直是預設,理由只有一個:還沒出現程式規則決定不了下一步的需求。
正向的訊號,出現在單一 endpoint 身上時值得認真評估 Agent:
if 仍然是程式在選邊,把 if 寫出來就好。)if 重新實作一個 planner,而且你有證據,因為你試過。反向的訊號,任何一條成立就先別動 Agent:
忍喵:拿需求先畫一遍流程圖,畫完發現沒有一個節點需要模型決定下一步——恭喜,你省下的不是開發時間,是之後每一天的維運。
決定讓渡控制流的那一刻,三個乘數一起到貨。
一、延遲與 token。 Agent 的成本單位是「輪」(round):模型產出 tool calls、程式執行、把結果送回模型,算一輪。一次模型 response 可以同時發出多個 parallel tool calls——它們共享同一輪模型呼叫,但每個 tool 有自己的延遲、費用與失敗。
把 N 定義成「依序把 tool 結果送回模型的 rounds」,一個 turn 就是 N + 1 次模型呼叫(最後一次產出最終答案),而且每一輪都重送長大中的 context。舉個明確假設下的例子:四輪 sequential rounds,就是至少五次循序推理之後,使用者才看得到最終答案的第一個字。Day 9 的計帳到了 loop 語意下,要乘上這個模型自己決定的 N。
忍喵:算錢的時候別只乘 N:loop 每一輪都重送一次越長越大的 context,input token 是一路疊上去的。N 輪的帳,比 N 倍更貴。
二、非確定性的範圍。 這個系列從 Day 2 就在講隔離非確定性,而模型取樣只是來源之一。code-owned 流程把這個來源的影響半徑限制在內容:最壞情況是答錯。Agent 把半徑擴大到執行路徑:選錯 tool、帶錯參數、不收斂。迭代上限是基本止血,官方指南講到單一 agent 時特別點名:set iteration limits(查核 2026-08)。
Prompt injection 的威脅模型也跟著升級:從「回覆裡出現不該有的字」升級成「攻擊者透過檢索內容影響 tool 的呼叫」。防護是 Day 21 的正題,這裡先把帳記上。
三、guardrail 重審。 Day 5 到 Day 15 的 guardrail 不是全部作廢,但要逐項重新分級。Redesign:依賴「一個 turn ≈ 一次呼叫」的合約,像 Day 5 的 timeout(per-step 還是 per-task?)與 Day 9 的預算檢查點(loop 每一輪之前,還是只在 turn 開頭?)。
Extend:性質還在、但要證明穿得過 tool 邊界,像 Day 15 的 principal threading(tool 呼叫 tool 的鏈上不能斷)與 Day 8 的 prompt provenance。Retain:本來就不綁單一呼叫的,像 Day 6 的 SSE 終端事件所有權。這份分級清單的逐項證明,就是 Day 18 的工作量。
倒是有一道當年就預留了位子:Day 6 定 SSE 詞彙時寫下「client 必須忽略未知的事件名」,就是為了讓 Part 4 的 tool 事件能用 additive 的方式加進來,不破壞既有 client。
判準通過之後,Agent 進到這個 backend,也只是又一個 adapter:
AgentService 住在 app-owned Protocol 後面,在 composition point 組裝。Day 18 規劃的是一個獨立的 Agent endpoint——handler 當然知道自己在跑 Agent use case;Protocol 藏的是 Agent Framework 與 provider 的型別,不是「有沒有 Agent」這件事。agent-framework-core==1.13.0 + agent-framework-openai==1.12.0。agent-framework 等於 agent-framework-core[all],而 [all] 恰好列了 30 個 optional integration 套件(PyPI metadata,查核 2026-08),本系列需要的只有 agent-framework-openai 一個。不用 meta 套件的原因,量得出來。store=False 明確支援。usage_details 的欄位名與 Day 9 的 TokenUsage 不同形(input_token_count 對 input_tokens 這類差異),要在 adapter 做欄位映射與驗證才接得回計帳,failed/disconnect 路徑與 ledger 的 commit 語意也要在 adapter 重審;細節與踩坑留給 Day 17。下次「做成 Agent」的提案出現時,先把這五題的答案寫下來:
五題都答得漂亮的提案,值得一個 Agent。大多數提案活不過第一題——那是判準在運作,不是判準失靈。
store=False、會聚合 loop usage。它不構成對框架成熟度的整體評估。Agent 與 chat 的分界不是任務難度,是控制流的所有權;判準是每一步走哪條邊由誰決定——程式規則決定得了就寫 code,這個 function-first 的方向由 Microsoft 自己的文件背書(「畫不畫得成圖」只是輔助 heuristic)。
Day 14 的 RAG 管線是判準的活教材:end-to-end 的保證(檢索必發生、回答只取材自過濾後來源、prompt 由程式組)換成 Agent 就從結構證明降級成 eval 與 trace 的維護對象;per-call 的 ACL filter 則靠 fail-closed 的 tool wrapper 保得住。真的需要 Agent 的訊號存在,但它到貨時帶著三個乘數,而 Day 5–15 的 guardrail 要逐項分級 retain/extend/redesign。
本篇 milestone 只有文件,沒有程式碼:docs/agent-decision-guide.md(day-16 tag)。
| 工程需求 | Azure / Microsoft 對應服務 | 本篇怎麼用 |
|---|---|---|
| 判斷 chat/pipeline/agent 的架構邊界 | Microsoft Agent Framework | 引用其官方判準(寫得出 function 就別用 agent;agents vs workflows 的分工)作為決策框架;程式碼 Day 17 落地 |
下一篇進 Day 17,Microsoft Agent Framework for Python 初探:拿一個真的通過五題的場景動手,順便講為什麼要 pin 版本。
這個框架在 python-1.0.0rc6 做過 provider 主導的 namespace 重構(升版指南,查核 2026-08):AzureOpenAIResponsesClient 對應改為 agent_framework.openai.OpenAIChatClient,相容 shim 其後移除。
Learn 的部分頁面至今仍引用舊類別——這種「文件追不上框架」的落差,本身就是評估新框架的重要訊號。
本文由作者規劃與撰寫,AI(Claude)協助草稿整理與程式碼驗證;技術內容與觀點由作者確認並負責。