Observed since 20 Oct, 21:44 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Track LLM costs across OpenAI, Anthropic, Gemini, LangChain, and 3500+ models with zero code changes
Zero-friction LLM cost tracking for OpenAI, Anthropic, Gemini, and LangChain applications.
AgentCost is an open-source LLM cost observability platform. This is the Python SDK — add two lines to your app and every OpenAI, Anthropic, Gemini, and LangChain call is tracked with model, tokens, cost, and latency, attributed to the agent that made it. Docs: agentcost.tech/docs/sdk · Project overview: github.com/agentcost-ai/agentcost
pip install agentcost
Or install from source:
cd agentcost-sdk
pip install -e .
from agentcost import track_costs
# 2 lines to add cost tracking!
track_costs.init(api_key="your_api_key", project_id="my-project")
# OpenAI — automatically tracked
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(model="gpt-4o", messages=[{"role": "user", "content": "Hello!"}])
# Anthropic — automatically tracked
from anthropic import Anthropic
client = Anthropic()
message = client.messages.create(model="claude-3-5-sonnet-20241022", max_tokens=100, messages=[{"role": "user", "content": "Hello!"}])
# Gemini — automatically tracked (Google Gen AI SDK)
from google import genai
client = genai.Client()
response = client.models.generate_content(model="gemini-2.5-flash", contents="Hello!")
# LangChain — automatically tracked
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4")
response = llm.invoke("Hello!")
create() (streaming and not), parse(), the OpenAI Responses API, Anthropic's messages.stream(), and LangChain invoke()/ainvoke()/stream()/astream() — sync and asyncagentcost connect claude-code or codex tracks every session on the machine, with no code at allagentcost analyze estimates cost and finds loops before you ship — entirely offlinetrack_costs.init(
# Required for cloud mode
api_key="sk_...",
project_id="my-project",
# Optional settings
base_url="https://api.agentcost.tech", # Your backend URL
batch_size=10, # Events before auto-flush
flush_interval=5.0, # Seconds between flushes
debug=True, # Enable debug logging
default_agent_name="my-agent", # Default agent tag
local_mode=False, # Store locally (no backend)
enabled=True, # Enable/disable tracking
# Custom pricing (overrides defaults)
custom_pricing={
"my-custom-model": {"input": 0.001, "output": 0.002}
},
# Global metadata (attached to all events)
global_metadata={
"environment": "production",
"version": "1.0.0"
}
)
Tag LLM calls by agent for granular analytics:
# Option 1: Set default agent
track_costs.set_agent_name("router-agent")
# Option 2: Context manager (recommended)
with track_costs.agent("technical-agent"):
llm.invoke("How do I fix this?") # Tagged as "technical-agent"
with track_costs.agent("billing-agent"):
llm.invoke("What's my balance?") # Tagged as "billing-agent"
Agent tagging answers which agent spent the money. Wrapping a multi-step run answers what one run costs, which step dominates it, and whether it loops:
with track_costs.workflow("support-triage"):
with track_costs.step("classify"):
llm.invoke("Which queue does this belong in?")
with track_costs.tool("search_docs"):
llm.invoke("Summarise these results")
with track_costs.step("draft_reply"):
llm.invoke("Write the response")
Every call inside shares one trace id and records its step, its parent and how
deeply it was nested. A nested workflow() joins the enclosing run rather than
starting a second one, and step() outside a workflow is a no-op — so
instrumenting a shared helper never depends on how it is called.
Entirely optional and entirely additive: without a workflow() your events are
exactly what they were before.
Mark how a run ended and you get cost per completed outcome, which charges failed runs to the successes they were paid for:
with track_costs.workflow("support-triage"):
ticket = handle(request)
track_costs.outcome(ticket.resolved, label=ticket.status)
Sent once per run when the workflow closes, so a late failure overwrites an earlier optimistic call.
Estimate what an agent will cost, and find its loops, before it has spent
anything. The agentcost CLI ships with the package and runs entirely on your
machine — no network call, and no file content outlives the token count taken
from it.
# What do the prompt and skill files cost on every call?
agentcost analyze ./agent --model gpt-4o
For a cost-per-run figure, record one representative run in local mode and project it to production volume:
import json
from agentcost import track_costs
track_costs.init(local_mode=True)
with track_costs.workflow("support-triage"):
run_agent(sample_request)
track_costs.flush()
json.dump(track_costs.get_local_events(), open("run.json", "w"))
agentcost analyze ./agent --events run.json --runs-per-day 2000
Prompt and skill files (gpt-4o)
3 file(s), 8,163 tokens, $0.020407 per call just to send them
Test run
3 run(s), 4.0 calls per run, $0.044000 per run (worst $0.044000)
$ 0.022000 50.0% 2.0 calls search_docs
$ 0.020000 45.5% 1.0 calls draft_reply
$ 0.002000 4.5% 1.0 calls classify
Projected at 2,000 runs/day: $2,640.00 per month
Findings (3)
[ high] Step 'search_docs' ran 2.0 times per run; a loop or retry will multiply this in production
[ high] 3 of 3 run(s) made the same call more than once (worst: 2x); the repeats are avoidable
[medium] 2 files have identical content; sending both pays twice for the same context
| Flag | Purpose |
|---|---|
--model | Model to price against (default gpt-4o) |
--events | Events from a local-mode run (JSON array or JSONL) |
--runs-per-day | Expected volume, to project a monthly cost |
--pattern | Glob to include; repeatable (defaults cover prompt and doc files) |
--json | Also write the full report as JSON |
--fail-on | Exit non-zero on a finding at or above this severity |
Use --fail-on high in CI to block a deploy on a cost regression:
agentcost analyze ./agent --events run.json --fail-on high
These agents already log every model call on the machine they run on. Connect one and its sessions appear in AgentCost with no instrumentation:
pip install agentcost
agentcost connect claude-code --api-key sk_...
agentcost connect codex
agentcost connect antigravity
connect imports the last 30 days and installs a hook, so each finished turn is
sent as it happens — from any folder, in the terminal, the IDE extension or the
desktop app. It is per machine: run it once on each one you work from. Codex
asks you to trust a new hook, so open Codex and run /hooks once.
Antigravity has no usage export and no hooks. Its figures are read from the
undocumented per-conversation database it keeps, so they are marked
estimated, carry no tool attribution, and are sent whenever another connected
agent syncs (or when you run agentcost sync on a schedule). Conversations from
before June 2026 are encrypted and cannot be read.
Look before you send anything — this needs no API key and no account:
agentcost sync --dry-run
Claude Code: would send 4,059 calls from 12 session(s), 1,915,622,519 tokens, $829.89 at API rates
$ 524.00 3,227 calls claude-opus-5-5
$ 213.11 525 calls claude-fable-5-1
Covered by a subscription: this is what the usage is worth, not a bill.
What you get in the dashboard:
| AgentCost | Coding agent |
|---|---|
Run (trace_id) | One session |
| Workflow | claude-code or codex |
| Agent | The main thread, or claude-code/<subagent type> |
| Tool | The tool each call invoked — Bash, Edit, an MCP tool |
| Session, user | session_id, and --user if you set one |
What leaves the machine: token counts, model, tool names, session id and
timestamps. Prompts, replies, tool arguments and tool output are never sent or
kept. Codex wraps each tool call in a short script;
that script is scanned for the tool name and nothing else is taken from it.
The working folder's name and git branch are sent only with --include-repo,
and never the path or the remote URL.
Session names. To see which chat cost the most by name instead of by id,
connect with --session-names. It sends the title Claude Code gives each chat
and the thread name Codex gives each thread. It is off by default because a
title is a short summary of what you asked. Chats that were already synced get
their names on the next sync.
Subscriptions. On a Claude or ChatGPT plan nothing is billed per token, so
the dollar figure is what the usage would cost at API rates. Each event says
which it is in metadata.billing (subscription or api), detected
automatically; --billing overrides it.
Tracking tells you what happened. --enforce stops it before it happens:
agentcost connect claude-code --enforce
This adds a second hook, the gate, that runs before every tool call and every
prompt and refuses the ones the project does not allow. It enforces the
guardrail you declare in the dashboard for the agent named claude-code or
codex (and claude-code/<subagent type>, which falls back to the main one):
| Boundary | Effect |
|---|---|
| Allowed tools | A tool not on the list is refused |
| Read-only | A tool tagged as writing is refused |
| Allowed models | Tool calls and prompts are refused while the session is on a model not on the list |
| Max tool calls per run | Further tool calls in that session are refused |
| Max cost per run | Further tool calls and prompts in that session are refused |
| Monthly budget, hard cap | New prompts are refused once it is spent — metered usage only, never a subscription |
The agent is told why, in words it can act on:
AgentCost guardrail: Write is not among the tools this project allows claude-code to use.
The gate never calls the network. It decides from the policy the last sync fetched and from the session's own log, so its totals are current to the call being judged, and it adds about a fifth of a second to a tool call. A guardrail changed in the dashboard applies from the next turn. If the gate cannot decide — no policy yet, a file it cannot read — it allows the call: it must never be the reason work stops.
All of it applies to Claude Code and to Codex. Codex's tools are reported under
the names its hooks use (its shell is Bash, file edits are apply_patch), so a
boundary written from what the dashboard shows is enforced as written. Per-run
limits are judged on the session's main thread. A model boundary takes effect
from a session's second prompt in Claude Code, which does not name the model
before its first call. Antigravity has no hooks and is never enforced.
The hooks never hold up a session: the sync hook starts the sync in a process of its own and returns, so the turn is sent even when the agent exits straight after it, as a headless run does.
| Command | Purpose |
|---|---|
agentcost | List every command and what it does |
agentcost connect <agent> | Start tracking. --enforce, --backfill-days, --include-repo, --session-names, --user, --billing, --base-url, --no-hook |
agentcost sync | Send what is new. --dry-run shows it instead |
agentcost status | What is connected and what is waiting |
agentcost disconnect <agent> | Remove the hook; data already sent stays |
A sync that is interrupted, repeated or run from several sessions at once never
loses a call or counts one twice: each log has a cursor that moves only after
the server accepted what came before it, and every call carries the provider's
own id as its event_id. Without the hook (--no-hook), run agentcost sync
yourself or on a schedule.
Claude Code and Codex can export their own telemetry, and the AgentCost backend
accepts it at /v1/otlp. Set it once in managed settings and every session in
an organisation reports in, including ones that run in the cloud and leave no
local log. Claude Code:
CLAUDE_CODE_ENABLE_TELEMETRY=1
OTEL_LOGS_EXPORTER=otlp
OTEL_METRICS_EXPORTER=none
OTEL_EXPORTER_OTLP_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.agentcost.tech/v1/otlp
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer sk_..."
OTEL_RESOURCE_ATTRIBUTES="agentcost.billing=subscription" # or api
Only the per-call usage records are read; everything else in the export is
discarded unread. A machine that also runs agentcost connect is counted once,
because both paths identify a call by its API request id. This path tracks
only — enforcement needs the hook.
Attach custom metadata for filtering/grouping:
# Persistent metadata
track_costs.add_metadata("user_id", "user_123")
track_costs.add_metadata("tenant_id", "acme_corp")
# Temporary metadata (context manager)
with track_costs.metadata(conversation_id="conv_456", step="routing"):
llm.invoke("Route this query")
Test without running a backend:
track_costs.init(local_mode=True, debug=True)
# Make LLM calls
llm.invoke("Hello!")
llm.invoke("World!")
# Retrieve captured events
events = track_costs.get_local_events()
for event in events:
print(f"Model: {event['model']}")
print(f"Tokens: {event['total_tokens']}")
print(f"Cost: ${event['cost']:.6f}")
Streaming calls are automatically tracked:
# Sync streaming
for chunk in llm.stream("Tell me a story"):
print(chunk.content, end="")
# Event recorded after stream completes
# Async streaming
async for chunk in llm.astream("Tell me a story"):
print(chunk.content, end="")
# Event recorded after stream completes
Each tracked event contains:
{
"agent_name": "my-agent",
"model": "gpt-4",
"input_tokens": 150,
"output_tokens": 80,
"total_tokens": 230,
"cost": 0.0093, # USD, real-time calculated
"latency_ms": 1234, # Measured latency
"timestamp": "2026-01-23T10:30:45.123Z",
"success": True,
"error": None,
"streaming": False,
"metadata": {"conversation_id": "conv_456"}
}
The SDK automatically fetches the latest pricing from the backend. This means:
# SDK automatically fetches pricing from backend
track_costs.init(
api_key="...",
project_id="...",
base_url="http://localhost:8000", # If running locally
)
# Prices are fetched once and cached
# GET http://localhost:8000/v1/pricing → {"pricing": {"gpt-4": {"input": 0.03, ...}}}
from agentcost.cost_calculator import refresh_pricing, update_pricing
# Force refresh from backend
refresh_pricing()
# Or manually set pricing (doesn't require backend)
update_pricing({
"my-custom-model": {"input": 0.001, "output": 0.002}
})
# Get all pricing
curl http://localhost:8000/v1/pricing
# Get specific model
curl http://localhost:8000/v1/pricing/gpt-4
# Update pricing (admin)
curl -X POST http://localhost:8000/v1/pricing \
-H "Content-Type: application/json" \
-d '{"gpt-4": {"input": 0.025, "output": 0.05}}'
Costs are priced for 3500+ models across 45+ providers, synced from the backend and updated automatically when pricing changes.
Pricing coverage is not the same as automatic tracking. Calls are intercepted for the four integrations shown in the Quick Start — OpenAI, Anthropic, Gemini and LangChain (which also covers LangGraph and CrewAI when they run through LangChain). Calling another provider's SDK directly produces no events; route it through LangChain, or report the usage yourself. The table below is what the SDK can put a price on.
| Provider | Models |
|---|---|
| OpenAI | gpt-4, gpt-4-turbo, gpt-4o, gpt-4o-mini, gpt-3.5-turbo, o1, o1-mini |
| Anthropic | claude-3-opus/sonnet/haiku, claude-3.5-sonnet/haiku, claude-4-opus |
| gemini-pro, gemini-1.5-pro/flash, gemini-2.0-flash/flash-lite, gemini-2.5-pro/flash/flash-lite, gemini-3-pro-preview | |
| Groq | llama-3.1-8b/70b, llama-3.2-3b, llama-3.3-70b, mixtral-8x7b |
| DeepSeek | deepseek-chat, deepseek-coder, deepseek-reasoner |
| Cohere | command, command-light, command-r, command-r-plus |
| Mistral | mistral-small/medium/large |
| Together AI | llama-3-70b/8b-chat, meta-llama models |
| Replicate | Various open-source models |
| OpenRouter | Aggregated models from multiple providers |
| Perplexity | pplx models |
| xAI | Grok models |
| Amazon | Amazon Nova, Titan models |
| Azure | Azure OpenAI models |
| AWS | Bedrock models (Claude, Llama, Mistral) |
| Anyscale | Anyscale endpoints |
| Cerebras | Cerebras models |
| Cloudflare | Workers AI models |
| Databricks | DBRX, Meta Llama models |
| DeepInfra | Various hosted models |
| Fireworks | Fireworks AI models |
| Hyperbolic | Hyperbolic models |
| Jina AI | Embedding models |
| Lambda | Lambda models |
| MiniMax | MiniMax models |
| Moonshot | Moonshot models |
| Sambanova | Samba models |
| Voyage | Embedding models |
| IBM | watsonx models |
| AI21 | AI21 Labs models |
| Aleph Alpha | Aleph Alpha models |
| Novita | Novita hosted models |
| Gradient AI | Gradient endpoints |
| Dashscope | Dashscope models (Alibaba) |
| Friendliai | Friendliai models |
| GMI | GMI models |
| Llamagate | Llamagate models |
| Morph | Morph models |
| NLP Cloud | NLP Cloud endpoints |
| Nscale | Nscale models |
| Oracle | OCI generative models |
| OVHCloud | OVHCloud models |
| Vercel | Vercel AI Gateway, v0 models |
| Weights & Biases | Wandb models |
| Zai | Zai models |
Note: The full list of 3500+ models is dynamically loaded from the backend. Run track_costs.init() with a valid API key to access all supported models.
stats = track_costs.get_stats()
print(f"Events sent: {stats['batcher']['events_sent']}")
print(f"Batches sent: {stats['batcher']['batches_sent']}")
track_costs.flush() # Send pending events
track_costs.shutdown() # Full shutdown
MIT License
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.3.1 | 14 Oct 2026 | 12 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 20 Oct, 21:44 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:7e6c8f74e259df4cdce294418cc6d5b24a8c932c0dea924141db15c32c492b9a