Package Information
Available Nodes
Documentation generating…
Documentation
n8n-nodes-gemini-token-telemetry
A drop-in replacement for n8n's Google Gemini Chat Model node that reports the token
usage the default node throws away — most importantly how much of your input came from
Gemini's context cache, which is billed at a fraction of the normal input rate.
If you run a long system prompt through an n8n AI Agent, cached input is probably the
single largest factor in your bill, and today it is invisible.
The problem
Google's API returns ten usage fields per call. Three reach your execution data. Two
independent layers drop the rest:
1. LangChain flattens it. @langchain/google-genai keeps the rich data on the message
(generations[0].message.usage_metadata, including input_token_details.cache_read) but
builds llmOutput.tokenUsage with only three fields:
llmOutput: { tokenUsage: {
promptTokens: usageMetadata.input_tokens,
completionTokens: usageMetadata.output_tokens,
totalTokens: usageMetadata.total_tokens,
} }
2. n8n deletes the message. N8nLlmTracing.handleLLMEnd runspick(g, ['text','generationInfo']) over the generations — dropping message, and with itusage_metadata. Crucially this happens before tokensUsageParser is called, so a custom
parser alone cannot recover anything.
This is not Gemini-specific. n8n's own Anthropic parser reads only input_tokens /output_tokens and ignores cache_read_input_tokens, which Anthropic does return.
What this node does
It subclasses the chat model and enriches llmOutput.tokenUsage inside _generate, before
any callback exists — so it does not depend on callback ordering, which is not guaranteed.
A matching tokensUsageParser then copies the extra fields into the persisted output.
No patching of n8n. No patching of LangChain. Only two options n8n already accepts per node.
Fields added to tokenUsage
| Field | Meaning |
|---|---|
cacheReadTokens |
input tokens served from Gemini's context cache |
inputTokensTotal |
total input tokens (cached + billable) |
inputTokensBillable |
inputTokensTotal - cacheReadTokens |
cacheHitRatio |
cacheReadTokens / inputTokensTotal |
outputTokensVisible |
candidatesTokenCount — the text the model actually returned |
reasoningTokens |
thinking tokens (billed as output) |
reasoningDerived |
true when reasoning was derived, not reported |
outputTokensBillable |
outputTokensVisible + reasoningTokens |
inputTokensOver200k / cacheReadOver200k |
long-context tier counts, when present |
usageSource |
gemini-token-telemetry-v1 — proves the value came from this node |
Two deliberate deviations
Reasoning is derived, not reported. Google's thoughtsTokenCount is not mapped by
LangChain into any field — but totalTokenCount includes it, soreasoning = total - input - visible output. reasoningDerived: true marks that this was
arithmetic, not a provider field.
completionTokens becomes billable output (visible + reasoning), with the original
value preserved in outputTokensVisible. Two reasons: it is the correct number for cost,
and it prevents total data loss — n8n only persists real usageif (tokenUsage.completionTokens > 0). When Google omits candidatesTokenCount the default
behaviour is to discard the real numbers and silently store a tiktoken estimate instead.
Measured
Against a 19,691-token stable prefix, second call (warm cache):
default node { promptTokens: 19691, completionTokens: 1, totalTokens: 19692 }
this node { promptTokens: 19691, completionTokens: 489, totalTokens: 20180,
cacheReadTokens: 16357, inputTokensTotal: 19691,
inputTokensBillable: 3334, cacheHitRatio: 0.8307,
outputTokensVisible: 1, reasoningTokens: 488,
reasoningDerived: true, outputTokensBillable: 489,
usageSource: "gemini-token-telemetry-v1" }
83% of the input was cached and previously invisible. Verified working inside an AI Agent
with tool calling, not only in a basic chain.
Cost (optional)
n8n already has a first-class cost slot: its default parser readstokenUsage.cost ?? tokenUsage.totalCost and propagates it to the tracing attributellm.cost.total. No provider fills it — the only occurrence of totalCost in the n8n codebase
is the line that reads it. This node fills it.
Prices come pre-filled. The package ships a built-in table for the Gemini models, copied
from the official pricing page, and each model
carries validity windows — so a promotional price switches over on its own date instead of
quietly becoming wrong:
| Model (Standard tier) | Input | Output (incl. thinking) | Cache read |
|---|---|---|---|
gemini-3.7-flash |
0.75 → 1.50 on 2027-01-01 | 3.75 → 7.50 | 0.075 → 0.15 |
gemini-3.6-flash |
0.75 → 1.50 on 2027-01-01 | 3.75 → 7.50 | 0.075 → 0.15 |
gemini-3.5-flash |
1.50 | 9.00 | 0.15 |
gemini-3.5-flash-lite |
0.30 | 2.50 | 0.03 |
Every cost row carries costPricingSource: builtin:<date the table was copied> ornode-parameters when you override. The number always says where it came from and how old
it is — that is the whole point of shipping a table instead of a constant.
Override by setting the three prices in the node's Options (per 1M tokens); yours win.
A model that is not in the table and has no prices set emits no cost field at all — an
absent number beats a zero that looks like a result.
⚠️ There is no pricing API. GET /v1beta/models returns twelve fields and none of them is a
price (measured: 53 models, zero price fields), and the Cloud Billing catalog rejects API keys
outright. So the table is a copy, and it has a date on it.
⚠️ Standard tier only. Batch is roughly half, Flex and Priority differ. The API does reportserviceTier in usageMetadata, but LangChain drops it before it reaches the node, so it
cannot be detected — Standard is assumed.
⚠️ The cache figure is the read price. Explicit caching also bills storage per hour,
which is not derivable from token counts. Implicit caching — what you get by default — has no
storage charge.
| Field | Meaning |
|---|---|
cost |
total for the call |
costInput / costCachedInput / costOutput |
the three components |
costCacheSavings |
what the cache saved versus paying full input price |
costCurrency |
a label only — no conversion is performed |
costPricingSource |
node-parameters |
If you leave the cached-input price at 0, cached tokens are charged at the full input
price. That errs upward on purpose, and costCacheSavings then reports 0 rather than
inventing a discount.
Measured on two consecutive calls with the same prompt, at 0.30 / 0.075 / 2.50 USD per 1M:
cold cache cost 0.00712980 cacheReadTokens 0 savings 0
warm cache cost 0.00349698 cacheReadTokens 16357 savings 0.00368033
The cache halved the cost of an identical call. That is the number the default node cannot show.
The Model field starts empty — on purpose
n8n's frontend strips any parameter whose value equals the node's declared default before
sending it. With a "helpful" default like models/gemini-3.6-flash, anyone picking that exact
model has their choice silently erased on save. The node keeps working — off the default — and
nobody can answer "which model is running?" by looking at the workflow.
That is not hypothetical: it happened in production here, on the node serving 100% of traffic,
and the detector meant to catch it was blind because it matched on the old node type.
With default: "", no legitimate choice can collide with the default, so every choice is stored.
Leaving it empty raises a clear error naming the fix, instead of calling the API with no model
and letting the provider return something confusing.
What's new
0.5.0 — the Model field no longer has a default, so the UI can never erase your choice.
0.4.0 — built-in Gemini price table with validity windows; cost now works out of the box.
0.3.0 — optional cost calculation from user-supplied prices.
0.2.0 — model list is now loaded from the Google API instead of a free-text field.
0.1.1 — added a node icon.
Install
n8n → Settings → Community nodes → Install → n8n-nodes-gemini-token-telemetry
Requires N8N_COMMUNITY_PACKAGES_ENABLED=true. In queue mode, also setN8N_REINSTALL_MISSING_PACKAGES=true so workers reinstall the package on boot when their
filesystem is ephemeral.
Use the existing Google Gemini(PaLM) API credential. Then point your Agent or Chain at
this node instead of the built-in Gemini model node.
Limitations
- Only the non-streaming path (
_generate) is instrumented. Streaming is untested. reasoningTokensis derived; if Google ever stops including thinking intotalTokenCount, it will read zero rather than wrong — but verify before relying on it.- The model list is a plain text field, not the dynamic dropdown of the built-in node.
- Tested against n8n 2.32.7 and
@langchain/google-genai2.1.24.
License
MIT