能應對真實輸入的 Agentic 工作流程
大多數代理示範之所以能運作,是因為示範輸入很乾淨。真實輸入並非如此。它們通常不完整、缺少欄位、帶有矛盾的上下文、PDF 實際上是影像,以及使用者在中途改變主意。從能正常運作的示範,到能無人值守執行六個月的工作流程,其差距幾乎完全取決於你如何拆解問題,以及把防護機制放在哪裡。
我在 BizFlowAI 每天都在設計多代理工作流程,下列模式是我反覆使用的。它並不華麗,但這正是我的內容管線能 24/7 運作而無需我監控,以及我的無伺服器 AWS 整合能達到 SLA 而非在凌晨兩點叫醒我的原因。
先把工作流程寫成確定性管線,再決定哪裡需要代理
在接觸任何 LLM SDK 之前,我會先假設代理不存在,把工作流程寫成一連串無聊的函式,具備型別化的輸入與輸出。這是代理設計中最有用的練習,卻也是大多數團隊會跳過的步驟。
以下是我對每一個新工作流程進行的拆解:
- 觸發條件是什麼? Webhook、定時任務、佇列訊息,或是使用者上傳檔案。寫出確切的 payload 結構。
- 最終產出是什麼? JSON 物件、已發布的貼文、Zendesk 工單更新,或資料庫的一筆紀錄。寫出確切的 schema。
- 中間產出有哪些? 在觸發條件與最終產出之間,列出所有必須存在的物件。每個物件都要有名稱與 schema。
- 針對每個產出之間的轉換,問自己:這需要判斷力,還是只是規則? 規則放在程式碼裡,判斷力交給 LLM。這就是全部的啟發式方法。
在我的 ContentStudio 管線中,流程如下:
trigger (topic gap detected)
-> ResearchBrief [LLM: judgment on angle + gap]
-> OutlineDraft [LLM: judgment on structure]
-> DraftPost [LLM: generation]
-> SEOAuditReport [code: deterministic checks]
-> RevisedPost [LLM: targeted rewrites only]
-> PublishPayload [code: schema validation]
-> published (WordPress API)
Enter fullscreen mode Exit fullscreen mode
請注意,有四個步驟是程式碼,三個步驟是 LLM。在較早的版本中,我讓 LLM 負責 SEO 稽核與發布 payload 組裝。它會幻覺產生 slug、捏造 canonical URL,還曾試圖發布到不存在的網站。把這些步驟改回確定性程式碼後,錯誤率幾乎降到零,整個管線也變得容易除錯。
我遵循的規則是:代理應該負責決策,而不是決策周邊的 plumbing。
挑選工具就像在招募,而不是在購物
工具選擇是大多數代理專案悄然出錯的地方。團隊給代理 15 個工具,因為「工具越多 = 能力越強」,然後卻納悶為什麼它有 30% 的機率挑錯工具。
根據實際上線代理的數據:
| 可用工具數量 | 正確工具選擇率 |
|---|---|
| 3-5 個工具,範圍明確 | 96-99% |
| 6-10 個工具 | 88-93% |
| 11-20 個工具 | 70-82% |
| 20 個以上工具 | 通常低於 70%,高度依賴 prompt |
這些數字來自我自己的管線,僅供參考,但模式在不同模型家族中都成立。解決方法不是換更聰明的模型,而是工具路由:用一個小型分派代理挑選子代理,每個子代理只配備 3-5 個工具。
撰寫工具規格時,我會把它當成職缺描述:
-
名稱:以動詞開頭、清楚明確。
search_customer_by_email,而不是customer_lookup。 - 描述:用一句話說明它做什麼,再用一句話說明什麼時候「不要」用它。「不要用」的說明,正是防止它自信地做出錯誤呼叫的關鍵。
- 輸入 schema:嚴格的型別,標註必填欄位。不要把「有預設值的選填欄位」隱藏成字串。
- 輸出 schema:每次都維持相同結構,包含錯誤結構。絕對不要讓代理去解析自由格式的錯誤字串。
如果你無法寫出「什麼時候不要用它」這句話,代表這個工具太寬泛,請把它拆開。
防護機制應該放在邊界,而不是放在 prompt 裡
Prompt 層級的防護(「不要編造客戶名稱」、「永遠驗證 email」)充其量只是建議。在正式環境中,我把它們當作備援,而不是主要防線。主要防線放在每一個步驟的邊界。
我總是會在四種邊界類型放置防護機制:
1. 工具呼叫的輸入驗證。 每個工具在做任何事之前,都會用嚴格的 schema(我在 TypeScript 用 Zod,在 Python 用 Pydantic)驗證輸入。如果代理幻覺產生了欄位,工具會回傳結構化的錯誤,讓代理可以根據回饋重試。沒有例外,也不會默默強制轉型。
2. LLM 回應的輸出驗證。 每個產生結構化輸出的 LLM 呼叫都會經過驗證器。如果需要結構化輸出,我會使用供應商的結構化輸出模式(Claude tool use、OpenAI response_format),然後再驗證一次。雙重保險。我曾經用這個方式抓到模型回歸。
3. 成本與迴圈上限。 每個工作流程都有硬性預算:最大 token 數、最大工具呼叫次數、最大執行時間。如果超過任何一項,就會明確失敗、寫入 dead-letter queue,並通知我。一個代理默默迴圈 40 分鐘、花掉 80 美元,這是只有在帳單上才會發現的 bug。
4. 副作用閘門。 任何會寫入真實世界的工具(寄送 email、發布貼文、建立工單、扣款),都會有獨立的授權檢查,且這個檢查「不在」LLM prompt 裡。它從 config 或 feature flag 讀取資料。這就是當你給代理寫入權限時,還能安心睡覺的原因。
以下是我使用的最小化副作用閘門模式:
async function publishPost(input: PublishInput, ctx: AgentContext) {
// 1. Schema validation
const parsed = PublishSchema.parse(input);
// 2. Authorization check (NOT in prompt)
if (!ctx.permissions.canPublishTo(parsed.siteId)) {
return { ok: false, error: "unauthorized_site" };
}
// 3. Idempotency: has this artifact already been published?
const existing = await db.publishedPosts.findOne({
contentHash: parsed.contentHash
});
if (existing) {
return { ok: true, alreadyPublished: existing.url };
}
// 4. Actual side effect, wrapped in retry with backoff
const result = await wpClient.publish(parsed);
await db.publishedPosts.insert({ ...parsed, url: result.url });
return { ok: true, url: result.url };
}
Enter fullscreen mode Exit fullscreen mode
在真正呼叫 WordPress API 之前,已經發生了四件事。每一個都曾在正式環境中抓到真正的 bug。
可觀測性是「它能運作」與「我能證明它能運作」之間的差別
如果你沒有事先做好儀表,就無法透過事後讀取日誌來除錯代理工作流程。而你一定會需要除錯,因為真實輸入會做出你預料之外的事情。
我為每一個工作流程附上的最低要求:
-
每個工作流程執行都有 trace ID,並傳遞到每一次工具呼叫、LLM 呼叫與資料庫寫入。只要用一個 ID 就能
grep。 - 結構化日誌,而不是 print 語句。JSON 包含 trace_id、step_name、duration_ms、token_in、token_out、cost_estimate 與 outcome。
- 持久化每一次 LLM 呼叫:prompt、回應、模型、temperature、token 數、延遲時間。儲存空間很便宜;無法重現一次糟糕的執行才昂貴。
- 持久化每一次工具呼叫:輸入、輸出、執行時間、錯誤。
- 每個工作流程都有執行摘要紀錄:觸發條件、最終狀態、總成本、總執行時間、產生的產物。
我使用簡單的 Postgres schema 來儲存這些資料。不是因為它很 fancy,而是因為我可以在出問題時用 SQL 查詢。當一次執行失敗時,我可以用一個查詢拉出完整歷史並重播。
我在所有工作流程中實際監控的指標:
| 訊號 | 為什麼重要 |
|---|---|
| 每個工作流程版本的成功率 | prompt 變更後的回歸 |
| 每個步驟的 p50 / p95 延遲 | 哪個步驟是瓶頸 |
| 每次執行的成本(token + 工具) | 單位經濟效益、預算警示 |
| 每個工具的工具呼叫錯誤率 | 哪個工具規格讓代理困惑 |
| 每個步驟的重試率 | 靜默的不穩定 |
| 人工介入率 | 自主性的真實衡量 |
最後一項是沒有人想看的。如果一個工作流程需要人工介入 15% 的時間,它就不是自主的,而是輔助的。沒關係,但要說清楚,並據此計算 ROI。
針對你已經看過的失敗模式進行設計
在建置足夠多的這類系統後,失敗模式會有規律可循。從第一天起就要針對它們進行設計。
自信但錯誤的輸出。 代理產生了看似合理但實際錯誤的內容。緩解方法:加入驗證步驟,用真實資料(資料庫查詢、規則檢查,或是高風險輸出時用第二個模型進行評判)檢查輸出。在我的內容管線中,每份草稿在發布前都會經過確定性的 SEO/AEO 稽核。如果失敗,就會回到針對性修正代理,並列出具體的失敗原因。
靜默的工具失敗。 工具回傳 200 但 body 是空的,或是看似成功的部分成功。緩解方法:工具必須回傳明確的 { ok: boolean, ... } 結構,而代理的工具使用迴圈必須把 ok: false 當作第一等情境處理,而不是當作字串去解讀。
失控迴圈。 代理不斷用略有不同的輸入呼叫同一個工具。緩解方法:在迴圈中追蹤最近的工具呼叫,當相同的 tool+input hash 重複出現時,注入系統訊息。同時也要有前面提到的執行時間上限。
上下文腐蝕。 長對話中,代理忘記或矛盾了先前的決策。緩解方法:不要跑一個很長的對話。把工作流程拆成多個步驟,每個步驟都有全新的上下文視窗,只把每個步驟需要的產物傳遞下去。這是多步驟代理設計中,可靠性最大的提升。
模型漂移。 供應商更新模型後,你的 prompt 悄悄失效。緩解方法:在程式碼中鎖定模型版本,並在每次部署時執行一小組評估。即使只有 20 個具代表性的案例,也能抓到大部分的回歸。
如果我明天要開始一個新的 agentic 工作流程,我會怎麼做
順序很重要。以下是我實際會做的:
- 先把工作流程寫成純粹的管線,不要有任何 LLM 呼叫。把 schema 寫對。
- 找出真正需要判斷力的 2-4 個步驟。其他所有步驟都保留為程式碼。
- 把工具規格寫得像職缺描述。如果一個代理有超過 5 個工具,就拆成子代理並加上路由器。
- 在每個工具和每個結構化 LLM 呼叫上加入嚴格的輸入/輸出驗證。
- 在連接到任何會寫入的東西之前,加入成本上限、迴圈上限與副作用閘門。
- 用 trace ID 為每一次呼叫做好儀表,並持久化完整歷史。
- 在出貨前,先用 20-50 個真實輸入(而不是合成資料)建立評估集。
- 在 feature flag 後面部署。先用 shadow mode 對生產流量執行一週。
- 觀察人工介入率。先針對介入率最高的步驟進行迭代。
- 然後才擴大範圍。
步驟 1-6 約佔總建置時間的 60%,卻能預防約 90% 的事故。跳過這些步驟,就是為什麼你的示範會在第二週崩壞。
我反覆看到的趨勢性錯誤是:團隊在還沒把工作流程寫成無聊的函式之前,就先挑選框架(LangGraph、CrewAI,或是這個季度流行的東西)。框架不是讓它運作的原因,拆解才是。
如果你正在建置一個必須對抗真實生產輸入的 agentic 工作流程,並且想要第二雙眼睛幫你檢視拆解或防護機制,我本季可以接幾個顧問案。請到 lazar-milicevic.com/#contact 聯絡我,或是在 blog 閱讀更多關於 RAG 評估、agentic 上下文,以及讓代理在與使用者接觸後仍能存活的相關文章。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.