
How to Attribute LLM Cost to Agents, Users, and Workflows
Most teams know their LLM spend for last month to the dollar. Few can say which agents, customers or workflows produced it. Provider dashboards report spend by API key and model, and nobody runs a business by API key.
LLM cost is hard to explain after the fact because nobody provisions it. A cloud bill maps to resources someone chose to create. An LLM bill is the sum of many small decisions made inside conversations and agent runs: a longer system prompt, a growing context window, a large tool result carried forward, a retry, a step routed to a bigger model. That is why spend often grows faster than usage, and why the gap doesn't show up on the provider dashboard as anything but a larger number.
The fix is on the telemetry side, not the billing side: record cost on each call, inside the trace, while the context still exists.
Record cost where the context is
flowchart LR
CALL["LLM call"]
CALL -- "billing export" --> BILL["monthly total<br/>by API key<br/><i>no context</i>"]
CALL -- "span attributes" --> SPAN["tokens + cost<br/>inside the trace"]
SPAN --> DIM["by agent · user ·<br/>workflow · team · outcome"]
DIM --> UE["<b>unit economics</b><br/>useful spend vs waste"]
style BILL stroke:#8e9aaa,stroke-dasharray:4 4
style SPAN stroke:#f4ae30,stroke-width:2px
style UE stroke:#59ca6f,stroke-width:2px
On every model call, record:
| What | Example attribute | Notes |
|---|---|---|
| Model and provider | gen_ai.request.model |
Record the model that actually answered, if routing can change it |
| Input and output tokens | gen_ai.usage.input_tokens, gen_ai.usage.output_tokens |
OpenTelemetry GenAI names; most agent SDKs emit them |
| Cached and reasoning tokens | provider-specific | Priced differently from normal input and output |
| Cost | cost_usd |
Compute at write time from a price table you version |
| Who and what | user_id, account_id, workflow, agent, team |
The dimensions you'll report on |
| Change context | release, prompt_version |
To tie spend changes to deployments |
Two details save a lot of confusion later:
- Version the price table. Prices change. Store the computed cost on the span rather than recomputing history with today's prices.
- Attribute your own overhead. Evaluation judges, embeddings, summarization jobs and retries are real spend. Give them their own
workflowvalue (for exampleevalorindexing) instead of letting them disappear into a shared bucket.
The dimensions worth having
| Dimension | What it shows |
|---|---|
| Agent or workflow | Which product area and owner the spend belongs to |
| User, account or tenant | Heavy users, abuse, and per-customer unit economics |
| Model and route | Whether expensive models are used where they make a difference |
| Prompt and release version | Prompt growth and spend changes after a deployment |
| Tool and retry count | Hidden loops and oversized tool results |
| Outcome | Spend that produced a result versus spend on failed runs |
Don't add dimensions you won't report on. Each one has to be set correctly by every service that makes model calls, and a dimension that's wrong 20% of the time is worse than none.
Report unit economics, not totals
Total spend can rise for good reasons, such as more users. Two ratios tell you more:
- Cost per completed workflow: total cost of runs in a workflow divided by the runs that reached a successful end state.
- Cost per successful outcome: the same, with "success" defined by the business (ticket resolved without escalation, invoice matched, lead qualified).
If either ratio rises, the system is getting less efficient at turning model calls into results, whatever the total is doing.
Check the expensive tail first

Sort runs by cost and look at the top few percent. A common finding is that a small share of runs accounts for a large share of spend, that those runs share a cause (often a retry loop or one oversized tool result in one workflow), and that they also complete less often. When that's the case, the fix is not a tradeoff: removing the loop lowers cost and raises completion at the same time.
What not to optimize first
Switching to a cheaper model, shortening prompts or capping conversation length are the most visible levers, and often premature. Cutting context the agent needed makes the bill smaller and the product worse. Downgrading a model in a workflow where failures go to human agents can lower the provider bill while raising the total cost of the workflow.
Start with spend that has no upside: retries, loops, oversized tool results, unused context, unnecessary escalation to larger models, and abandoned sessions that used expensive tokens before failing. Once those are under control, decisions about routing and prompt size are safer, because you can see the quality and outcome tradeoff. The cost reduction guide has a worked example.
Doing this with Anosys
The Anosys SDKs for OpenAI, Anthropic and the agent frameworks record tokens (including cached and reasoning tokens) on every span, and get_user_context attaches the user and session. OpenTelemetry sources and OpenRouter Broadcast carry the same fields, and coding tools such as Claude Code and Codex are covered by their own hooks. Your workflow, team and outcome fields are mapped once in Collecting → Field Mapping, so dashboards can show cost per workflow, account or outcome next to evaluation scores, and an alert can fire on cost per completed run rather than on the monthly total.
See Track AI and coding spend to get started.
Free plan, no credit card
See your LLM cost per agent, user and workflow
Connect your SDK or OpenTelemetry source and break spend down by agent, user, workflow and model, with the expensive tail one click away.