讓 agent 自己跑批次任務之前,我以為成本是可以「事後再看」的東西。
那是一個排程任務:每天半夜起來,把前一天累積的一批項目逐筆讓 agent 處理、分類、寫回。我設好就睡了。隔天早上看帳單,一個晚上燒掉的量,差不多是我平常手動用一整個月的程度。
東西是有跑完,但這個價錢完全不合理。我花了點時間把它拆開來看,發現失控的不是「AI 很貴」這個籠統印象,而是三、四個很具體、而且都能堵住的洞。這篇就是那幾道護欄。
先搞清楚錢是怎麼流掉的
LLM 的計費單位是 token,輸入和輸出分開算,而且輸出通常比輸入貴好幾倍。先記住這條核心關係:你每次呼叫付的錢 ≈(這次塞進去的 input token + 吐出來的 output token)× 對應模型的單價。實際帳單還有快取、推理 token 之類的細項,但抓大放小,主導成本的就是這三個變數——而每一個我那晚都用錯了。
把這條公式攤開來算一次就很清楚。假設我那個任務每次呼叫平均塞 50K token 的 context,跑 200 筆,光 input 就是 1,000 萬 token。如果我還傻傻地全程用最貴的模型——以我寫這篇的當下,旗艦模型的單價大約是最小模型的十幾倍(實際比例請以各家官方價目為準,這裡只看數量級)——那這 1,000 萬 token 的帳,等於用小模型跑要花的十幾倍。任務內容一模一樣,只因為我選錯模型、塞太多 context,價差就是一個數量級。
形容詞在這裡沒有意義。「AI 好貴」是錯的結論,「我用一個數量級的浪費,去跑一個根本不需要那麼貴的任務」才是真相。
護欄一:用對模型,別拿大砲打蚊子
我那晚最蠢的決定,是整批都用旗艦模型。
實際上 200 筆裡面,有 180 筆是「判斷這筆屬於哪一類」這種瑣事,剩下 20 筆才需要真的動腦推理。我卻讓最貴的模型去做那 180 筆分類,等於請一個資深架構師整晚在那邊蓋橡皮章。
修法是加一層路由(routing):先用便宜的小模型判斷這筆難不難,簡單的就地解決,只有它自己舉手說「這題我吃不準」的才升級到大模型。
1 | def route(task): |
光是這一層,那 180 筆從旗艦單價掉到小模型單價,帳單立刻瘦一圈。一個常見的分法是:分類、抽取、格式轉換、簡單問答交給小模型,跨檔案推理、架構決策、需要長鏈思考的才給旗艦。
但路由本身會看走眼——小模型判「難不難」也是會判錯的。所以分流的安全方向是「拿不準就往上送」:與其讓小模型硬接一個它其實搞不定的任務、產出爛結果還要重跑,不如讓它在猶豫時直接升級到旗艦。誤判往「貴」的方向偏,比往「爛」的方向偏便宜得多。
護欄二:設硬上限,到頂就停
第二個洞更危險:我整個 loop 沒有任何花費上限。
那晚有幾筆項目資料是髒的,agent 處理失敗就重試。失敗、重試、又失敗、再重試,每一次重試都是一次完整的付費呼叫,而且它把前面失敗的對話也帶進去,context 越滾越大、越重試越貴。沒有人喊停,它就真的一直試到天亮。
agent 不會自己心疼錢。你不給它上限,它就沒有上限。
所以要在外層包一個預算守門員,花掉的累計到頂就強制停,而不是寄望任務「自然跑完」:
1 | BUDGET = 2_000_000 # 這次任務的 token 總預算,到頂就停 |
關鍵不是 BUDGET 設多少,而是「有沒有這個 if」。有了它,最壞情況是「跑不完,剩幾筆明天再處理」;沒有它,最壞情況是「跑到天亮,帳單三位數」。前者我隔天補一下就好,後者只能心痛。
這道護欄其實有三層,缺一層就漏:
- 單筆重試上限(
MAX_RETRY = 2):一筆髒資料最多試兩次,還不行就跳過、記下來人工處理,別讓它無限吃預算 - 單次輸出上限(
max_tokens):每次呼叫都設輸出長度上限。很多人盯著 input,卻忘了帳單常常是炸在「模型一口氣吐了超長回覆」——尤其開了推理模式時,沒設上限等於開放式燒錢 - 整批預算上限(上面那個
if):所有花費累計到頂就停
護欄三:別每次都把整本歷史塞進去
第三個洞最隱形。我的 loop 為了讓 agent「有上下文」,每次呼叫都把前面所有處理過的紀錄一起帶進去。
聽起來很合理,實際上是災難。第 1 筆帶 1 筆的量,第 200 筆帶 199 筆的量,單筆的 input 隨進度線性長大。把整批加起來更可怕——1+2+…+200,總成本是筆數的平方等級,不是線性。後半段每一筆都比前半段貴上好幾倍,而那些舊紀錄,對「處理當前這一筆」其實一點用都沒有。
我一開始還懷疑是不是模型本身變慢變貴了,盯著單價看半天,後來把每次呼叫的 input token 印出來,才發現是自己親手讓 context 雪球越滾越大。(順帶一提,我也試過反方向矯枉過正——把 context 砍到完全不帶,結果 agent 失憶到連任務格式都記不住,輸出全亂。砍 context 不是砍越多越好,是砍掉「對當前這筆沒用」的部分。)
修法是只帶當前這筆真正需要的東西,固定大小,不隨進度累積:
1 | # 錯:context 隨進度線性膨脹 |
如果任務之間真的有依賴,需要參考前面的結論,那就只摘要保留必要的幾條,而不是原封不動把整本歷史搬過去。
護欄四:重複的部分,快取起來
前三道堵的是浪費,第四道是把不得不付的部分再壓一層。
我那個任務每一筆呼叫,前面都掛著一大段一模一樣的系統提示——角色設定、規則、輸出格式範例,每筆都重新送、重新計費。200 筆就把同一段東西付了 200 次。
現在主流的 LLM API 大多支援 prompt caching:固定不變的前綴第一次正常計費,後續命中快取的部分用大幅折扣計價。對「同一套規則跑很多筆」這種場景,省下來的相當可觀。但有三個細節不搞清楚,你會以為自己省了其實沒省:
- 觸發方式各家不同:OpenAI 是自動判斷相同前綴、你什麼都不用做;Anthropic 要你在 request 裡用
cache_control明確標出哪段要快取。機制不一樣,照抄會踩空 - 有最低門檻:前綴要夠長(通常上千 token 起跳)才會進快取,太短不吃
- 有時效:快取活不久(Anthropic 預設約 5 分鐘,OpenAI 官方沒給準數、實測也是幾分鐘等級),間隔太久前一次的快取就過期、得重新建。所以這招對「短時間內密集連發」最有效,零星呼叫吃不太到
要讓快取命中率高,把不變的東西放前面、會變的放後面:
1 | [固定前綴] 系統提示 + 規則 + 輸出格式範例 ← 每次都一樣,放最前面 |
順序很重要。快取是從頭開始比對前綴的,你把會變的東西夾在固定內容中間,前綴一被打斷,後面的快取就全部失效。
還有一招:能等就走批次
如果你的任務跟我一樣是「一批東西、不急著即時拿到結果」,那最划算的一招其實是 Batch API。OpenAI、Anthropic 都有非即時的批次模式:你把一整批請求丟進去,它慢慢跑完(官方掛保證 24 小時內,實際通常幾小時就好),換來的是單價大約打對折。
我那個排程任務根本是半夜跑、早上才看結果,完全不需要即時——卻用逐筆即時呼叫的全價去跑,等於白白多付一倍。能等的任務走批次,這一刀砍下去的幅度,比前面任何一道護欄都直接。
收一下
那次之後我重跑同一個任務,護欄都補上,帳單從「一個月的量」掉回「一個晚上該有的量」,任務照樣跑完。回頭看,這幾件事沒有一件是高深技術:
- 用對模型——簡單任務別用旗艦,加一層便宜的路由分流,拿不準就往上送
- 設上限——單筆限制重試、單次限制輸出、整批限制預算,三層都要有
- 控制 context——每次只帶這筆需要的,別讓歷史雪球滾大
- 快取固定前綴——搞清楚各家機制和時效,把重複的部分壓下去
- 能等就走批次——非即時任務用 Batch API,單價直接對折
但有一件事我想單獨講,因為它比上面全部都重要。
先量,再改。
我那晚之所以一度懷疑錯方向(以為是模型變貴),就是因為一開始沒有把每次呼叫的用量印出來。等我老老實實 log 了每筆的 model、input/output token、重試次數,洞在哪裡一眼就看到了。沒有數據之前的所有優化,都只是猜。先把用量攤在眼前,你會發現要堵的洞,通常比想像中少,也比想像中好堵。
下次再讓 agent 自己跑通宵之前,我會先確認那個預算守門員的 if 在不在。它不在,我就不睡。




