跳至主要内容

Day 21:Agent(一) — 能用工作流就不要用代理

· 閱讀時間約 6 分鐘
Willis Chen
Tech Instructor

RAG 讓模型「知道」,Agent 讓模型「能做」。今天進入第二高頻的任務訊號(47.6%)。

但我要先講一個可能讓你意外的建議:你需要的多半不是 Agent。

今天要解決的問題​

2025 年以來,「Agent」變成了一個什麼都能塞的詞。實務上多數的失敗是這樣的:

團隊做了一個「文件處理 Agent」,讓它自己決定要先分類還是先擷取、要不要查資料庫、要不要再確認一次。結果:同樣的文件,這次花 4 次 LLM 呼叫、下次花 11 次;成本浮動三倍,處理時間從 8 秒到 40 秒不等;出錯時完全不知道為什麼。

而這個流程本來是固定的——分類 → 擷取欄位 → 寫入資料庫。三個步驟,寫死就好。

用不確定性換來了不需要的彈性。

📊 職缺訊號

「Agent、工作流程與工具串接」出現在 47.6% 的生成式 AI 職缺中,僅次於 RAG。注意這個任務的完整名稱——它包含「工作流程」。市場要的不是「會做 autonomous agent」的人,是能判斷什麼時候該用哪一種的人。


一、現象:自主性是一道光譜​

從工作流到代理的自主性光譜

圖 21-1:從工作流到代理的自主性光譜(三條曲線為作者依實務經驗歸納的示意評分)。每往右一步,換到彈性,失去可預測性與可除錯性,成本也上升。

這張圖的讀法:

  • 左半部(工作流區間):流程由你決定,模型只負責它擅長的單點任務(分類、擷取、生成文字)。可測試、可預期、成本穩定。
  • 右半部(代理區間):流程由模型決定。能處理你沒預想到的情境,代價是每次執行路徑都可能不同。

判斷準則很簡單:

如果你能事先畫出流程圖,就用工作流。 只有當「下一步該做什麼」真的取決於前一步的結果,且組合太多無法窮舉時,才需要代理。

二、原理:五種設計模式,先試前四種​

Anthropic 在《Building Effective Agents》整理了一組實用的模式體系。前四種都屬於工作流:

image

圖 21-2:從工作流到代理的六種設計模式(示意架構)。實務上八成的需求,前四種就能解決。

模式適用情境例子
提示鏈步驟固定、每步都需要 LLM翻譯 → 潤稿 → 檢查術語一致性
路由有明確分類、各類處理方式不同客服分流:退貨/技術問題/帳務
平行化多個獨立子任務、可同時進行同時檢查一份合約的五個風險面向
協調者-工作者子任務數量不固定,但類型已知依查詢改寫出的 N 個子問題分別檢索
評估者-最佳化者有明確的品質標準可自我檢查生成程式碼 → 跑測試 → 依錯誤修正
自主代理步驟數與順序無法預先決定「幫我調查這個客訴的來龍去脈」

三、動手:手刻一個 ReAct 迴圈​

即使你最後用框架,手刻一次也是值得的——因為所有框架都是在包裝這 30 行。

"""agent.py —— ReAct(Reasoning + Acting)的最小完整實作。"""
import json

SYSTEM = """你是一個能使用工具的助理。你可以呼叫工具來取得資訊或執行動作。

工作方式:
1. 思考需要什麼資訊才能回答
2. 呼叫適當的工具(一次一個)
3. 觀察結果後,決定要繼續呼叫工具還是給出最終答案

原則:
- 只用工具回傳的事實回答,不要編造
- 若工具回傳錯誤,讀懂錯誤訊息後調整參數重試(最多兩次)
- 若三次仍無法取得資訊,誠實告知使用者"""

TOOLS = [
{"type": "function", "function": {
"name": "search_kb",
"description": "在公司知識庫中搜尋資料。當需要公司政策、產品規格、"
"作業流程等內部資訊時使用。",
"parameters": {"type": "object", "properties": {
"query": {"type": "string", "description": "搜尋關鍵字或問題"}},
"required": ["query"]}}},
{"type": "function", "function": {
"name": "calculate",
"description": "執行數學運算。當需要精確計算時使用——不要自己心算。",
"parameters": {"type": "object", "properties": {
"expression": {"type": "string", "description": "Python 算式,如 (1200*0.05)"}},
"required": ["expression"]}}},
]

def execute_tool(name: str, args: dict, ctx: dict) -> dict:
"""工具執行:所有安全檢查都在這裡,不在提示裡。"""
if name == "search_kb":
chunks = retrieve_and_rerank(args["query"], user=ctx["user"]) # 帶權限
if not chunks:
return {"error": "查無相關資料,請換個關鍵字或確認問題範圍"}
return {"results": [c["text"][:500] for c in chunks[:3]]}
if name == "calculate":
try:
# 正式環境請用安全的算式解析器,不要用 eval
return {"result": safe_eval(args["expression"])}
except Exception as e:
return {"error": f"算式無效:{e}"}
return {"error": f"未知的工具:{name}"}

async def run_agent(question: str, ctx: dict,
max_iterations: int = 6, max_cost_tokens: int = 20_000):
"""ReAct 迴圈。兩個上限是必要的護欄,不是可選項。"""
messages = [{"role": "system", "content": SYSTEM},
{"role": "user", "content": question}]
trace, used_tokens = [], 0

for i in range(max_iterations):
resp = await client.chat.completions.create(
model=MODEL, messages=messages, tools=TOOLS)
used_tokens += resp.usage.total_tokens
msg = resp.choices[0].message

if not msg.tool_calls: # 模型認為可以回答了
trace.append({"step": i, "action": "final_answer"})
return {"answer": msg.content, "trace": trace,
"iterations": i + 1, "tokens": used_tokens}

messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = execute_tool(call.function.name, args, ctx)
trace.append({"step": i, "tool": call.function.name,
"args": args, "ok": "error" not in result})
messages.append({"role": "tool", "tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False)})

if used_tokens > max_cost_tokens: # 成本上限:防止失控燒錢
return {"answer": "這個問題較複雜,已轉由專人處理。",
"trace": trace, "stopped": "cost_limit"}

return {"answer": "我無法在合理步驟內完成這個查詢,已轉由專人處理。",
"trace": trace, "stopped": "max_iterations"}

這段程式碼有三個生產級的細節,比迴圈本身更重要:

  1. max_iterations 與 max_cost_tokens——沒有這兩個上限,一個迴圈錯誤就能燒掉你整個月的預算。這不是可選項。
  2. trace——記錄每一步做了什麼。出問題時,這是唯一能讓你重建現場的東西(Day 22 詳談)。
  3. 權限在 execute_tool 裡——不在提示裡。這點 Day 17 強調過,Day 25 會看到不這樣做的後果。

四、取捨:什麼時候升級到代理​

訊號判斷
「流程圖畫得出來」用工作流
「大部分情況固定,少數需要判斷」工作流 + 一個路由節點
「步驟數不固定,但工具種類已知」協調者-工作者,或限制迭代次數的代理
「連要用哪些工具都不確定」自主代理——但先問自己這個需求是不是定義不清
「使用者的請求千奇百怪」通常代表還沒收斂需求,先做前 20% 高頻情境

4.1 從工作流開始,逐步放寬的實際做法​

即使最終要做代理,也建議這樣分階段推進:

階段一:全部寫死。 流程固定,模型只做單點任務(分類、擷取、生成文字)。此時你會累積兩樣東西:真實的使用者請求分布,以及哪些情境是流程處理不了的。

階段二:加一個路由。 從階段一的資料中,你會發現請求其實集中在少數幾類。用一個 LLM 路由節點分流,各類走各自的固定流程。這一步通常能覆蓋 80–90% 的請求。

階段三:對剩下的長尾開放代理。 那 10–20% 無法歸類的請求,才交給有工具、有迭代上限的代理處理。而且要記錄它的每一次執行——這些紀錄會告訴你,其中有沒有哪些又可以被收斂成固定流程。

這個順序的好處是:每一階段都能上線、都能產生價值,而且風險是遞增而非一次到位。它也符合 Day 11 談過的原則——自動化程度應該隨著你對系統的理解而提高。

💡 一個我常用的說服方式

當有人堅持要做全自主代理時,我會問:「這個代理如果做錯,最壞會怎樣?」

  • 如果答案是「回答錯誤,使用者再問一次」——那可以放心試。
  • 如果答案是「刪掉資料 / 寄錯信 / 下錯單」——那就先做工作流,把不可逆的動作交給人確認。

自主性的上限,應該由「錯誤的代價」決定,而不是由技術的可能性決定。


今日小結​

  • 自主性是光譜不是開關:每往右一步,換到彈性,失去可預測性與可除錯性,成本也上升。
  • 能畫出流程圖就用工作流;只有當「下一步取決於前一步結果、組合無法窮舉」時才需要代理。
  • 六種模式中,前四種(提示鏈、路由、平行化、協調者-工作者)就能解決八成需求。
  • ReAct 迴圈的核心只有 30 行,所有框架都是在包裝它——手刻一次會讓你在除錯時知道從哪查。
  • 三個生產級細節比迴圈本身更重要:迭代上限與成本上限(不是可選項)、trace(唯一能重建現場的東西)、權限在執行層而非提示裡。
  • 自主性的上限應由「錯誤的代價」決定,不是由技術可能性決定。

明天預告​

明天談 Agent 的第二半,也是決定它能不能上線的部分:MCP 標準怎麼降低整合成本、多代理系統的真實失敗模式,以及一套可靠性護欄——怎麼讓一個會自己做決定的系統,不會把公司燒了。

延伸閱讀​

  • 📄 Anthropic, Building Effective Agents——本文模式體系的來源,「先用簡單的」這個核心主張值得每個做 agent 的人讀一次
  • 📄 Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models (2022)——ReAct 的原始論文

← Day 20 | Day 22 →

本文同步發布於 iThome 鐵人賽;Blog 版本為站內閱讀與系列導覽的主要版本。