Skip to content
← 文章目次 HCYTLOG / 文章與筆記

多步驟 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
  1. 01
    驗證與快取完成

    固定 60 ms

  2. 02
    獨立工作匯合完成

    3 個槽,最後一項在 380 ms 完成。

  3. 03
    LLM完成

    匯合後才執行 800 ms。

  4. 04
    回應完成

    1200 ms 完成。

  • 請求驗證0–40 ms(耗時 40 ms)

    快取命中也不能跳過必要驗證

  • 回應快取查詢40–60 ms(耗時 20 ms)

    未命中,繼續計算

  • 脈絡檢索60–240 ms(耗時 180 ms)

    執行槽 1;與其他兩項沒有相依

  • 工具資料60–380 ms(耗時 320 ms)

    執行槽 2;與其他兩項沒有相依

  • 既有洞察讀取60–300 ms(耗時 240 ms)

    執行槽 3;與其他兩項沒有相依

  • LLM 生成380–1180 ms(耗時 800 ms)

    等待三項獨立工作全部完成才開始

  • 回應序列化1180–1200 ms(耗時 20 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

效能測試應固定資料集、負載形狀、版本與部署資源,把冷啟動、快取未命中與穩定狀態分開。外部模型難以完全固定時,先用可控制延遲的替身驗證排程,再用真實依賴觀察整體分布。替身能證明依賴與同時執行上限是否符合設計,不能證明正式環境達到目標。

一次只改一項策略,再比較端到端延遲、錯誤率與成本。若資料取得已縮短,總時間卻沒有足夠改善,就回到時間軸,看剩下哪一段限制了使用者等待。下一次修改應該根據那個證據選擇。

延伸閱讀

SDK 文件參考日期:2026-10-07。

系列導覽

上一篇:資料分群與洞察。下一篇:Agent 任務 API。也可以從產品架構開始。