n8n 成本帳本:我如何為每次工作流程執行標記成本
The Cost Ledger: How I Gave Every n8n Workflow Run a Price Tag
作者為每次 n8n 工作流程中的 LLM 呼叫記錄 token 用量和美元成本,並每日按工作流程彙總支出。昨日支出達該工作流程自身 7 日中位數 2 倍時,系統會向 Slack 發出警報;一次重試政策變更觸發 3.1 倍警報,作者當天收緊重試條件。成本按 API 回傳的 token 用量和價格表計算,價格表可能隨供應商調價而過時;帳本可定位異常工作流程,但原因仍需人工排查。
Eleven workflows calling LLMs, one monthly API bill, and nothing in between. When a prompt change tripled my model spend overnight, I found out thirty days later -- from the invoice. So I built a cost ledger: every LLM call in every workflow logs its tokens and its dollar cost, and a daily query tells me exactly which workflow is burning money. Here's the setup, the alert rule, and the incident that paid for it.
The shadow-run post covered how I test a new workflow version against live traffic. It mentioned a cost bound in passing -- fifty shadowed events, roughly two dollars of extra model spend. Two readers asked the obvious follow-up: how do you know it's two dollars? What's your actual LLM spend, and per what?
Honest answer at the time: I didn't know. I had a per-provider invoice and a gut feeling. That's not an answer, that's a shrug.
The missing layer
n8n executions log token usage for some AI nodes, buried in execution data you have to open one run at a time. There's no aggregation: no "workflow X spent $Y this week," no per-node attribution, no trend. The invoice tells you the total. Nothing tells you the distribution.
And distribution is where the surprises live. My worst surprise wasn't the total going up -- it was one workflow, the inbox triage agent, quietly going from $0.40/day to $1.30/day after I "improved" its classification prompt. The improvement made the prompt 2.4x longer and added a second model call per email for "edge cases." Nobody noticed for a month. The workflow worked fine. It just worked fine expensively.
The ledger: one Code node after every LLM call
The design is boring on purpose. Right after every node that calls a model -- OpenAI node, Anthropic node, an HTTP call to an inference endpoint -- sits a Cost node. One Code node, same shape everywhere, writes one row per call:
// Cost node: append one row per LLM call
const call = $input.first().json;
const PRICE = {
'gpt-4o-mini': { in: 0.15, out: 0.60 }, // USD per 1M tokens
'gpt-4o': { in: 2.50, out: 10.00 },
'claude-3-5-sonnet': { in: 3.00, out: 15.00 },
'claude-3-5-haiku': { in: 0.80, out: 4.00 },
};
const p = PRICE[call.model];
const row = {
run_id: $execution.id, // n8n execution id
event_id: call.event_id, // my own correlation key
workflow: $workflow.name,
node: call.node_name,
model: call.model,
in_tokens: call.usage.prompt_tokens,
out_tokens: call.usage.completion_tokens,
usd: (call.usage.prompt_tokens * p.in +
call.usage.completion_tokens * p.out) / 1e6,
ts: new Date().toISOString()
};
// insert into cost_log (run_id, event_id, workflow, node, model, in_tokens, out_tokens, usd, ts)
Three details that matter:
- The price table lives in the node, not in config. Prices change rarely; when they do I edit one shared Code snippet (it's a shared sub-workflow, imported everywhere). I don't want a database lookup between the model call and the log row -- the ledger must never slow down or break the workflow it observes.
-
It logs the intended model call, not a guess. Token counts come from the actual API response (
usage), not from counting words in my prompt. If a provider doesn't return usage (some proxy endpoints don't), the node logsin_tokens: nulland ausd_estimatedflag -- estimated rows never feed the alert query, only the dashboard. - Every AI agent workflow gets one. No exceptions for "this one barely calls the model." The workflows that barely call the model are exactly the ones where a prompt edit quietly doubles the token count.
The morning query
A scheduled workflow runs this once a day at 07:00 and posts the result to Slack:
-- daily spend per workflow, vs its own 7-day median
WITH daily AS (
SELECT workflow, DATE(ts) AS d, SUM(usd) AS spend
FROM cost_log WHERE ts >= NOW() - INTERVAL 14 DAY
GROUP BY workflow, DATE(ts)
),
medians AS (
SELECT workflow, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY spend) AS med
FROM daily WHERE d < CURRENT_DATE GROUP BY workflow
)
SELECT d.workflow, d.spend, m.med,
ROUND(d.spend / NULLIF(m.med, 0), 2) AS spike_ratio
FROM daily d JOIN medians m ON d.workflow = m.workflow
WHERE d.d = CURRENT_DATE - 1;
The alert rule is one line: spike_ratio >= 2.0 fires a Slack alert with the workflow name and yesterday's spend. Not total spend -- ratio against its own median. A $0.30 workflow that suddenly costs $0.90 is the same class of problem as a $30 workflow that suddenly costs $90. Both mean something changed and nobody reviewed it.
The morning digest itself is a table, not a chart: workflow, yesterday, 7-day median, ratio. I read it in thirty seconds with coffee. Charts are for quarterly reviews; tables are for Tuesday mornings.
The incident that paid for it
Two weeks after wiring the ledger, the alert fired on my repurposing workflow -- spike_ratio 3.1, $2.10 vs a $0.68 median. The cause: a retry policy change I'd made the day before (retry on a broader class of failures, from the retry-budget post). Broader retries meant the workflow re-ran its two most expensive model calls on failures that used to be terminal. The retry change was correct for reliability. It was also, quietly, a 3x cost change. Without the ledger I'd have learned this from next month's invoice. With the ledger I learned it over coffee and tightened the retry condition the same morning.
That's the whole value proposition: the ledger doesn't reduce cost. It converts "the invoice went up" (monthly, unactionable) into "this workflow changed its behavior yesterday" (daily, actionable). Cost control follows from visibility; visibility is all the ledger does.
What the ledger is not
It's not precise accounting. Token counts from usage are exact; my price table is a snapshot that drifts when providers change prices. The ledger answers "which workflow, roughly how much, trending which way" -- it's an instrument panel, not a tax return.
It's not a prompt optimizer. It will never tell you why a workflow got expensive -- the retry incident needed me to go read the diff. The ledger narrows the search to one workflow and one day. Diagnosis is still human work.
And it's not free: every model call now writes a database row. At my volume that's a few hundred rows a day, trivial. If you're doing millions of calls a day, sample (log 1 in 100, multiply). The pattern survives sampling; the alert rule just needs the sampling rate in the denominator.
I'm building AI Automation Lab -- n8n workflow templates for AI automation -- and keeping a free lab on GitHub. I also send occasional notes on n8n reliability patterns: get them here.
來源:DEV Community · dev.to