新帳號 免費註冊,送 10 次呼叫,最高 $1,免綁卡。

LLM 代理為什麼這麼慢?用追蹤找出 3 個原因

逐一重現並修正 AI 代理執行緩慢的三個原因:循序呼叫模型、自己撰寫的程式碼中有慢速工具,以及對只需一行的回答啟用推理。模型呼叫修正後,耗時從 3.7 秒降至 1.8 秒,減少 1.9 秒;另兩項則分別處理工具執行過慢與簡短回答不必要的推理。

我們的推薦

即時價格
模型 結論 價格
GPT-6 Luna 5 次單句回答在預設推理強度下的耗時中位數為 25.6 s,將 reasoning_effort 設為 none 時則為 10.2 s,輸出 token 數從 1,150 降至 190。 示範中的主要代理 低至 $0.1/百萬
DeepSeek V4.1 Flash 3 次單句檢查使用相同的 127 個輸入 token,依序送出時的耗時中位數為 3.7 s,一起送出時則為 1.8 s。 示範中的子代理 低至 $0.3/百萬
目錄
  1. 代理執行很慢,該從哪裡查起?
  2. 可以同時執行的呼叫,卻逐一執行時,追蹤會長什麼樣子?
  3. 怎麼判斷慢的是自己的程式碼?
  4. 如果慢的是某一次模型呼叫呢?
  5. 本文沒有涵蓋哪些問題?
  6. 常見問題
  7. 如何找出 LLM 代理最慢的步驟?
  8. 並行呼叫 LLM 比循序呼叫更貴嗎?
  9. 回覆很短,為什麼模型呼叫還是很慢?
  10. 如何量測修正是否讓代理變快?
  11. 閘道能顯示兩次模型呼叫之間,我的程式碼做了什麼嗎?
  12. 延伸閱讀

代理變慢,通常有三種原因:模型呼叫本來不必互相等待,卻被排成循序執行;兩次呼叫之間,自身的程式碼跑得太慢;或是某次模型呼叫做了超出該步驟需求的工作。追蹤會把每次呼叫畫在時間軸上,也會顯示呼叫之間的空檔,幾秒內就能看出是哪一種。本文用一個小型客服代理重現這三種問題,再逐一修正,並在 Synthorai 主控台中並排比較修正前後的執行結果。

TL;DR

  • 用 asyncio.gather 同時送出三個互不相依的模型呼叫,中位數耗時從 3.7 s 降到 1.8 s,成本不變。
  • 同時執行四次模擬的 1.1 s 資料庫查詢,代理自身程式碼的耗時從 4.6 s 降到 1.3 s。這部分無法靠更換模型改善。
  • 讓 GPT-6 Luna 回答五個只需一句話的問題時,關閉推理(reasoning_effort="none")可將耗時從 25.6 s 降到 10.2 s,輸出 token 數從 1,150 降到 190。

本文延續追蹤 LLM 代理的設定。該指南說明了如何用三個請求標頭,把模型呼叫串成一筆追蹤。請先依照指南設定範例環境(Python 3.10 或更新版本,並啟用 virtualenv),再從 github.com/synthorai-io/use-cases 的同一個目錄執行三個案例:

cd use-cases/agent-tracing
python incidents.py serial-checks slow-tool reasoning

每個案例都會執行兩次,先跑有問題的版本,再跑修正版。每次執行會印出一行結果,包含追蹤 ID:

serial-checks
  before   3.5 s   3 calls     127 in (     0 cached)     60 out  $0.000110   trace-d2bb96ebcf04
  after    1.5 s   3 calls     127 in (     0 cached)     62 out  $0.000112   trace-5f1e76a2de87

模型每次呼叫的延遲都會波動,因此我們在 2026 年 10 月 11 日將每個案例各執行五次。下表列出中位數,括號內是最快與最慢的執行時間。第三個案例的修正版有一次失敗,因此其中位數取自四次執行。上面的範例輸出與截圖中的比較,各來自另外一次執行,不包含在這五次測試中。時間均為腳本量測的實際經過時間,也就是從開始到結束的耗時。

案例追蹤呈現的現象修正方式修正前修正後
serial-checks長條呈階梯狀,彼此沒有重疊同時送出互不相依的呼叫3.7 s(3.5 到 4.5)1.8 s(1.7 到 2.0)
slow-toolyour app 列很長修正自身程式碼11.0 s(9.5 到 12.2)9.2 s(6.7 到 11.0)
reasoning輸出 token 數遠高於回覆長度降低推理強度五個回答共 25.6 s(19.0 到 31.8)10.2 s(7.9 到 12.1)

代理執行很慢,該從哪裡查起?

先看追蹤頂端的摘要。它會將總耗時分成閘道內的時間,以及應用程式內的時間。總耗時就是使用者實際等待的時間。「In the gateway」指至少有一個模型請求尚未結束的時段。「In your app」則是其餘時間:請求之間只有你的程式碼在執行的空檔。這部分也包含程式碼與閘道之間的網路傳輸時間;本文的執行結果中,每次呼叫約占零點幾秒。

根據這個拆分,就能決定下一步要查哪裡:

  • 大部分時間在閘道內,長條呈階梯狀:呼叫在互相等待。請看第一個案例。
  • 應用程式內的時間占比很高:問題不在模型。請看第二個案例。
  • 大部分時間在閘道內,而且有一條特別長的長條:某次呼叫占了大部分時間。摘要會將它標為「slowest」。請看第三個案例。

要確認修改的效果,先開啟修正前的追蹤,選擇 Compare,再選取修正後的追蹤。主控台會在同一頁呈現兩次執行:每個指標都有 A、B 與差異;同名呼叫會配對比較;兩張瀑布圖也會共用同一條時間軸。本文所有圖片都來自這個檢視畫面。

可以同時執行的呼叫,卻逐一執行時,追蹤會長什麼樣子?

瀑布圖會呈階梯狀:前一條長條結束,下一條才開始。範例代理使用 DeepSeek V4.1 Flash,針對客戶訊息執行三項只需一句話回答的檢查:情緒、政策與風險。這三項檢查都不需要其他檢查的結果,但有問題的版本卻在迴圈中逐一等待:

for span, question in CHECKS.items():        # each call waits for the one before it
    await check(run, span, question)

只要改一行:

await asyncio.gather(*(check(run, span, q) for span, q in CHECKS.items()))

兩張共用時間軸的瀑布圖。追蹤 A 中,sentiment、policy-check 與 risk 三次呼叫呈階梯狀,在 2.9 s 結束,每次呼叫之間都有一小段應用程式空檔。追蹤 B 中,同樣的三次呼叫互相重疊,全部在 1.3 s 內結束

追蹤 A 的總耗時是 2.9 s:三次呼叫各花 0.8 s,逐一執行,彼此之間還有 0.2 s 的空檔。追蹤 B 的長條互相重疊,總耗時是 1.3 s。同一個主控台頁面上,截圖區域上方的比較表顯示,總耗時從 2.9 s 降到 1.3 s,減少 53.4%(以未四捨五入的時間計算)。腳本對這組執行量到的時間是 3.5 s 與 1.5 s,因為它也計入建立連線,以及每次呼叫的網路傳輸時間。

兩次執行的輸入都是 127 個 token。成本會隨輸出量變化,而每次執行的輸出長度略有不同:這組執行的成本是 $0.000110 與 $0.000112;五次執行的中位數則是循序版 $0.000105、並行版 $0.000109。並行呼叫收送的 token 相同,因此不會增加成本。

檢查自己的代理時,可以逐一查看相鄰的兩條長條,確認第二次呼叫是否用到了第一次的輸出。如果沒有,就可以讓它們重疊執行。前後有相依關係的呼叫,例如先產生計畫,再執行計畫中的步驟,仍然必須依序執行。

怎麼判斷慢的是自己的程式碼?

your app 列會很長。這個案例中,有問題的版本會先從四張資料表查詢客戶資料,再回答問題,而且一次只查一張表。每次查詢都用 1.1 s 的 sleep 模擬資料庫呼叫:

rows = [await lookup(t) for t in tables]     # four queries, one at a time

同樣只要改一行,就能同時執行四次查詢:

rows = await asyncio.gather(*(lookup(t) for t in tables))

兩張共用時間軸的瀑布圖。追蹤 A 中,0.7 s 的 classify 呼叫之後,是以虛線表示的 4.6 s 應用程式列,再接著是 8.0 s 的 answer 呼叫。追蹤 B 中,應用程式列縮短為 1.3 s,answer 呼叫則是 8.5 s

修正的那一列從 4.6 s 縮短到 1.3 s。比較表顯示「In your app」減少 71.8%,同樣是以未四捨五入的時間計算。更換模型或修改 prompt 都無法改善這一段:當時沒有任何模型請求尚未結束,代理只是在等待自己的查詢。

總耗時的變化就沒這麼單純。這組執行從 13.3 s 降到 10.5 s;修正本身省了 3.3 s,整體卻只省了 2.8 s。少掉的半秒來自 GPT-6 Luna 的 answer 呼叫:第一次輸出 471 個 token,第二次輸出 566 個,耗時分別為 8.0 s 與 8.5 s。表中的五次執行,兩個版本的時間範圍有重疊:修正版為 6.7 到 11.0 s,有問題的版本為 9.5 到 12.2 s。因此,單次修正版執行仍可能比單次未修正版更慢。

所以應該比較你修改的那一列,而不是只比較單次執行的總耗時。比較畫面的逐次呼叫表會將對應呼叫排在一起,以 A 與 B 顯示時間、輸出量和成本。若要確認端到端效能是否改善,應比較兩個版本各自多筆追蹤的執行時間。Logs 中的 p95 延遲(95% 的請求能在此時間內完成)不會反映這項修正。它量測的是單次請求,而這裡省下的是請求之間的時間。

閘道只能看見空檔持續多久,無法知道你的程式碼在這段時間做了什麼。如果空檔很長,而且原因不明,就該用 OpenTelemetry 為自己的函式加上 span。

如果慢的是某一次模型呼叫呢?

先看那次呼叫產生了多少 token,因為產生輸出需要時間。推理模型會先思考再回答,即使可見回覆只有一句話,思考過程仍會算入輸出 token 的費用與耗時。這個案例中,有問題的版本向 GPT-6 Luna 提出五個簡短的政策問題,要求「用一句話回答」,但保留預設推理強度。修正版只增加一個參數:

await call(run, "answer", model=PLANNER_MODEL, messages=messages, reasoning_effort="none")

兩筆各含一次呼叫的追蹤比較。總耗時為 4.0 s 與 1.4 s,輸出 token 數為 306 與 49,推理 token 數為 268 與 0,成本為 $0.0002 與 $0.000033。下方是逐次呼叫比較列,以及畫在同一時間軸上的兩次 answer 呼叫,第一筆的輸出數量加了底線

這是五個問題中第一題的修正前後比較。兩次回覆都只有一句話。使用預設推理強度時,模型為了產生這句回答,輸出了 306 個 token,其中 268 個用於推理,耗時 4.0 s。關閉推理後,輸出 49 個 token,耗時 1.4 s。這次呼叫的成本從 $0.00016 降到 $0.000033;主控台會將前者四捨五入顯示為 $0.0002。

五個單句回答(五次執行的中位數,第二列為四次)時間輸出 token 數成本
預設推理強度25.6 s1,150$0.000614
reasoning_effort="none"10.2 s190$0.000134

不必開啟比較畫面,也能發現這個問題。瀑布圖有 Output 欄位;當某次呼叫的輸出量較大,而且大部分用於推理時,數量下方會加上底線,上面的 306 就是如此。選取該呼叫後,面板會在輸出 token 數下方,用獨立一列顯示推理 token 數。

查詢、分類、改寫工具結果這類步驟,適合關閉推理。需要多步推理的任務則不適合,而本範例並未測試這類任務。我們的 GPT-6 Luna 實測列出各設定下,每個回答的推理 token 數與成本;推理強度指南則列出各模型接受哪些值。請依步驟調整,並檢查調整前後的回答。

本文沒有涵蓋哪些問題?

有兩項。第一項是使用者感受到的回覆速度。串流不會縮短一次呼叫的總耗時,但使用者能更早看到文字;對聊天回覆來說,這往往才是重點。呼叫面板中的 First token 欄位就是用來觀察這件事。這些追蹤中的欄位是空白,因為範例沒有使用串流。

第二項是代理以外的原因:例如供應商在那個時段變慢,或速率限制讓用戶端必須等待並重試。這些問題會呈現為一條很長的長條,或一次失敗呼叫之後接著重試,無法靠調整程式碼結構解決。

常見問題

如何找出 LLM 代理最慢的步驟?

開啟該次執行的追蹤,查看摘要:它會標出最慢的呼叫,並將總耗時拆成閘道內與應用程式內的時間。下方瀑布圖會以長條呈現每次呼叫,因此最長的長條或最長的 your app 列,就是要檢查的步驟。

並行呼叫 LLM 比循序呼叫更貴嗎?

不會。範例中的三項檢查,循序送出的成本中位數為 $0.000105,並行送出為 $0.000109,輸入同樣是 127 個 token;差異來自每次執行的輸出長度不同。成本取決於 token 數,而並行呼叫收送的 token 相同。需要留意的是速率限制,因為並行呼叫會同時送達。

回覆很短,為什麼模型呼叫還是很慢?

通常是模型先進行了推理。推理會產生輸出 token,但這些 token 不會出現在可見回覆中。GPT-6 Luna 的五個單句回答,在預設推理強度下,輸出 token 數中位數為 1,150;使用 reasoning_effort="none" 時則為 190。

如何量測修正是否讓代理變快?

比較你修改的部分,不要只看一次執行的總耗時。範例中,同時執行四次查詢,讓自身程式碼的耗時從 4.6 s 降到 1.3 s;但因為其中一次模型呼叫的耗時會波動,修正後各次執行的總耗時仍介於 6.7 s 到 11.0 s。應以追蹤的執行時間,比較兩個版本的多次結果。

閘道能顯示兩次模型呼叫之間,我的程式碼做了什麼嗎?

它會用 your app 列顯示空檔持續多久,但不會顯示這段時間內發生了什麼。若要查看自身程式碼內的 span,請使用 OpenTelemetry SDK,並將它的 traceparent 標頭一路傳遞,讓兩個檢視畫面共用同一個追蹤 ID。

延伸閱讀

← 應用情境