多步驟 Agent 任務太慢?先找關鍵路徑,再處理快取
Agent 可能反覆呼叫模型、查詢資料與執行工具。用互動時間軸找出關鍵路徑,區分串流、完整回答快取與 prompt caching,避免只縮短畫面等待卻放大執行成本。
本文目錄
產品中的 Agent 完成一項任務,可能先讀取對話記憶、查工具資料,再讓模型決定下一步。每多一次模型與工具往返,使用者等待與成本就會累積。要讓這類功能可用,需要同時看見第一段回覆、完整任務完成時間,以及整個任務消耗的資源。
假設產品的 Agent 正在替使用者整理問題並查詢相關資料,回覆很慢,CPU 使用率卻不高。你把資料庫索引補上、把執行個體加倍,回答仍然等很久。這不一定表示修改沒作用。可能資料庫只佔整段等待的一小部分,模型生成才是主要耗時;也可能請求在等連線,而 CPU 圖根本看不到。
我會先把使用者等待的範圍定清楚:從送出請求到收到第一段內容,還是到整個回答完成?串流能改善前一項,後一項仍受生成時間影響。把兩者混成一個「回應時間」,會讓不同調整的效果無法比較。
關鍵路徑與使用者等待
畫出依賴,才知道哪些可以平行
Agent 的下一個工具可能取決於上一個結果,這種依賴必須等到結果回來才能判斷。已知彼此獨立的唯讀工作,則可以交給應用程式平行處理,不必為每一項都多走一次模型決策。
圖解的未命中請求包含驗證、快取查詢、三個獨立工作、生成與回覆整理。三個工作分別取得對話脈絡、工具資料與既有洞察;生成需要這些結果,就必須等它們都結束。把所有步驟丟進 Promise.all,不會讓資料依賴消失。
若三個獨立工作的耗時是 R、U、I,生成是 G,其餘必要步驟合計為 B,理想循序時間是 B + R + U + I + G。在資源足夠、三個工作能同時開始時,平行時間接近 B + max(R, U, I) + G。這個簡化公式忽略排程與網路額外成本,目的是辨認限制整體完成時間的關鍵路徑。
三個互相獨立的工作可以分派到有限的執行槽;LLM 仍要等它們全部完成。快取命中走另一條路徑。
此排程的總延遲為 1200 ms。LLM 在 380 ms 才開始,因為必須等所有前置工作完成,而不是只等最快的那一項。
- 總延遲(教學)
- 1200 ms
- 實際獨立工作槽
- 3
- 相較循序未命中
- 少 420 ms
- 01驗證與快取完成
固定 60 ms
- 02獨立工作匯合完成
3 個槽,最後一項在 380 ms 完成。
- 03LLM完成
匯合後才執行 800 ms。
- 04回應完成
1200 ms 完成。
圖中的資料只用來說明流程,不是實際服務的測試結果。 假設三項工作獨立、耗時固定,採先到先排的排程。命中的是可直接回傳且已符合版本、權限與安全條件的回應快取;未計入資源競爭、取消與快取失效成本。
圖中的各階段耗時是教學設定,不是實際產品或 SDK 的效能量測。在圖解切換完整回答快取的命中與未命中。命中時,略過脈絡、工具、洞察檢索與模型生成,直接整理快取結果回覆;未命中時,才執行完整流程。再比較循序與平行執行,調整同時執行上限,觀察長條起訖。執行名額不足時,部分工作仍需等待;生成也不能早於必要資料就緒。
程式可以先用最小的依賴結構表達這件事:
async function answer(input: ValidInput) {
const [memory, toolResult, insights] = await Promise.all([
retrieveMemory(input),
runReadOnlyTool(input),
retrieveExistingInsights(input),
]);
return generate({ input, memory, toolResult, insights });
}
這段假設輸入已驗證、工具為唯讀,而且三項工作互不依賴。正式系統還需要全服務的同時執行上限,不能每個請求都無上限地新增工作。若工具需要檢索結果,就應保留順序。若某個工具會寫入外部系統,重試與取消的規則也不能直接套用到它。
串流提早顯示進度,不代表任務提早完成
OpenAI Agents SDK(@openai/agents)的 run(agent, input, { stream: true }) 可在執行中提供事件,讓產品逐步顯示文字或工作進度。仍要等 stream.completed,並檢查是否停在等待核准,才能判斷這次執行是否完成。串流能讓使用者較早看到內容,但不會消除工具等待或保證總耗時下降。OpenAI:Running agents
Claude Agent SDK(@anthropic-ai/claude-agent-sdk)則可用 includePartialMessages: true 取得主 session 的文字片段。介面需要避免把片段與後續完整訊息都追加一次;子 Agent 的 token 級更新也不會由這個選項轉送,因此進度介面不能假定所有工作都會持續產生文字。Claude:Streaming output
工具目錄很大時,OpenAI API 的 tool_search 可以延後載入需要的工具定義,減少一次帶入的內容;代價是多一個尋找工具的步驟。因此「輸入 token 變少」不等於「任務一定變快」。產品測試要同時比較任務完成率、模型往返次數與端到端延遲,再決定哪些工具一開始就提供、哪些需要時才載入。OpenAI:Tool search
快取與執行成本
快取省掉的工作,必須真的可以省
快取命中帶來的效益取決於命中什麼。圖解採用完整回答快取,省下生成之前的資料工作與生成本身;若改成只有檢索快取,就只能省下檢索。完整回答較容易混入過時資訊或不同使用者的內容,這項正確性取捨沒有被時間軸自動解決,也不能由圖解推算正式服務的命中率。
Agent 若會寫入外部系統,完整回答快取還不能冒充「這次已執行動作」。快取可以重用查詢結果或說明文字,但是否真的修改資料,要回到本次任務的執行紀錄。
供應商的 prompt caching 又是不同層次。OpenAI 的機制重用相同提示前綴已處理的部分,後續新輸入與回答仍要生成;它不會像圖中的完整回答快取一樣略過整段工作。維持固定指引與工具定義有助於前綴重用,但是否命中仍要看實際用量,不能只因沿用同一個對話就假定有快取。OpenAI:Prompt caching
想實作這一層快取,可以接著看淺談 Prompt Cache。該篇用互動圖解對照第一次寫入與後續前綴重用,並整理 OpenAI、Anthropic、Gemini、DeepSeek 的 SDK 範例、TTL、命中欄位與成本。
快取鍵至少要考慮租戶、授權範圍、資料版本與輸入。若兩位使用者問同一句話,卻能讀取不同文件,單純使用問題文字作為鍵就可能洩漏結果。模型、提示版本與工具參數也可能影響完整回答,不能只以「文字相同」判定可重用。
TTL 讓資料在一段時間後失效,卻不等於正確性保證。權限撤銷或來源資料變更,可能需要立即失效,或用版本鍵避免讀到舊值。也要分清楚「沒有快取」與「快取值為空」:用 if (cached) 判斷,會把合法的空字串、零或否定結果當成未命中。
同一個熱門鍵過期時,多個請求可能同時重算。可以合併同一程序內的重複工作,但跨執行個體還需要共享協調,或容許重複計算並限制總量。加一把鎖並不自動完成設計:鎖需要到期、錯誤釋放,以及寫入結果時避免舊工作覆蓋新版本。
平行工作與佇列各自有成本
同時執行多項工作通常把等待重疊,卻增加同時使用的連線與外部 API 額度。當下游只能承受固定工作量,更多請求會排隊或被拒絕。若逾時後又立即重試,原工作還在執行,總負載可能繼續增加。每次工作都應消耗同一份截止時間預算,並在支援時傳遞取消訊號。
多步驟 Agent 還要限制模型往返與工具呼叫次數,避免「繼續查一下」無止境地累積成本。產品可以在預算用完時停止,回覆已完成哪些步驟與哪些結果尚未取得;取消後也要檢查工具是否已產生外部副作用,不能把中止顯示當成自動復原。
背景佇列可以把洞察整理、批次摘要移出聊天的等待路徑。但代價會轉到另一份契約:多久開始處理、失敗怎麼重試、任務能不能重複執行,以及使用者如何知道結果。API 很快回覆「已接受」,不能拿來宣稱任務處理變快。
我會同時記錄排隊時間與工作執行時間。若執行耗時穩定,但排隊時間增加,問題可能是容量或工作分配;若兩者都增加,還要檢查下游退化。只看 worker 的處理時間,會漏掉使用者實際等了多久。
調整前先留下能比較的量測
對每個階段量測耗時,記錄模型往返次數、工具呼叫次數、快取命中、輸入與輸出 token 數、成功或逾時結果,再看整體分布。平均值可能被大量快取命中的請求拉低,未命中的尾端延遲仍然很高。P95 描述該批樣本的第 95 百分位,不等於每位使用者都有同樣等待,也不能省略樣本數與觀察期間。
成本資料也要先確認計算範圍。Claude Agent SDK 的 total_cost_usd 是客戶端估算,不能當成正式帳單。要怎麼累計,取決於是否接續同一個 session,以及 SDK 底層使用的 Claude Code 版本:
- 獨立呼叫:沒有使用
resume或continue時,每次query()分開計算。要統計多次呼叫的總成本,可以把各次結束時的總額相加。 - 接續同一個 session,Claude Code 2.1.277 起:程序正常結束時會保存成本,接續時恢復先前總額。要看 session 總成本,讀取最新結果;要估算這次任務新增多少,則比較同一累計區段開始與結束時的差額,不能把歷次結果全部相加。
- 接續同一個 session,Claude Code 2.1.277 之前:透過 SDK 或
claude -p接續時,成本從零起算,各次結果只涵蓋當次呼叫,因此多次呼叫的總額需要自行加總。
串流輸入模式的一次 query() 也可能產生多個 result,不能把每個結果都當成獨立呼叫相加。同一累計區段讀最新總額;若發送 /clear、/reset 或 /new,累計會重新起算,要保留每次重設前的最後總額,不能跨重設直接算差額。正式帳務仍回到供應商的用量與帳務資料。Claude:Cost tracking
Claude Agent SDK 的 maxBudgetUsd 可以限制這次 query() 新增的花費,不包含接續 session 先前的成本;/clear 也會重新起算這份預算。產品若有每日或每位使用者的額度,仍要在自己的任務紀錄中累計與檢查,不能把單次執行的限制當成整個帳號的預算管理。Claude:Cost tracking
資料庫慢時可以看查詢計畫。PostgreSQL 的 EXPLAIN ANALYZE 會實際執行查詢並提供量測,因而要留意寫入副作用,先在可控制的測試資料上分析。索引是否有用,需要看查詢條件、讀取資料量與寫入成本,不能只看索引是否存在。PostgreSQL 18:Using EXPLAIN
效能測試應固定資料集、負載形狀、版本與部署資源,把冷啟動、快取未命中與穩定狀態分開。外部模型難以完全固定時,先用可控制延遲的替身驗證排程,再用真實依賴觀察整體分布。替身能證明依賴與同時執行上限是否符合設計,不能證明正式環境達到目標。
一次只改一項策略,再比較端到端延遲、錯誤率與成本。若資料取得已縮短,總時間卻沒有足夠改善,就回到時間軸,看剩下哪一段限制了使用者等待。下一次修改應該根據那個證據選擇。
延伸閱讀
- OpenAI Agents SDK:Running agents:串流與執行完成的判斷。
- Claude Agent SDK:Streaming output與 Cost tracking:文字片段、成本累計範圍與版本差異。
- OpenAI:Tool search:工具定義的載入策略。
- OpenAI:Prompt caching:可重用的提示前綴與快取用量。
- PostgreSQL 18:Using EXPLAIN:查詢計畫與實際執行量測。
SDK 文件參考日期:2026-10-07。
系列導覽
上一篇:資料分群與洞察。下一篇:Agent 任務 API。也可以從產品架構開始。