Complete guide to integrating AgentCost into your OpenAI, Anthropic, Gemini, and LangChain applications
Install the AgentCost SDK using pip:
pip install agentcostOr install from source:
cd agentcost-sdk
pip install -e .Add just two lines of code to start tracking LLM costs:
from agentcost import track_costs
# Initialize tracking
track_costs.init(
api_key="sk_...", # Settings → your project → API Key
project_id="123e4567-e89b-42d3-a456-426614174000" # Settings → your project → UUID
)
# 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, world!") # Automatically trackedNote: The SDK uses monkey patching to intercept OpenAI, Anthropic, Gemini, and LangChain calls. Your existing code requires no modifications.
Security: API keys are shown once on creation. Store them securely and rotate keys from the dashboard if needed.
Force-send any pending events, then check your dashboard — your first calls should appear within seconds:
# Push any batched events to the backend immediately
track_costs.flush()
# Now open your dashboard — the calls above should be there.Nothing showing up? If your api_key and project_id don't match (e.g. the project name was used instead of its UUID), the backend returns 403 and the SDK emits a RuntimeWarning plus an error on the agentcost logger. Check your console output.
The SDK supports extensive configuration options:
track_costs.init(
# Required for cloud mode
api_key="sk_...",
project_id="123e4567-e89b-42d3-a456-426614174000", # project UUID, not its name
# 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"
}
)| Parameter | Type | Default | Description |
|---|---|---|---|
| api_key | str | None | Your project API key |
| project_id | str | None | Your project's UUID (Settings → your project), not its name |
| batch_size | int | 10 | Events before auto-flush |
| flush_interval | float | 5.0 | Seconds between flushes |
| local_mode | bool | False | Store events locally only |
| debug | bool | False | Enable debug logging |
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 bug?") # Tagged as "technical-agent"
with track_costs.agent("billing-agent"):
llm.invoke("What's my balance?") # Tagged as "billing-agent"Agent names appear in your dashboard, allowing you to track costs per agent and identify which parts of your system are most expensive.
Agent tagging answers which agent spent the money. Wrapping a multi-step run answers what one run costs, which step inside it is expensive, and whether the agent is looping:
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 the step it belongs to, its parent, and how deeply it was nested. Steps nest freely, and a sub-agent that opens its own workflow() stays part of the caller's run rather than starting a second one.
This unlocks the Workflows page in your dashboard: cost per run rather than per call, cost per step and per tool, and detection of the same call being made twice inside a single run — which is usually a loop rather than something a cache would fix.
Mark how a run ended and you also 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)Entirely optional and entirely additive. Without a workflow() your events are exactly what they were before, and step() outside a workflow is a no-op — so instrumenting a shared helper never depends on how it gets called. Workflow, step and tool names are strings you write and they are transmitted as written; see the privacy architecture page.
Estimate what an agent will cost, and find its loops, before it has spent anything. The analyser runs entirely on your machine:
# What do the prompt and skill files cost on every call?
agentcost analyze ./agent --model gpt-4o
# Record a test run with local mode, then project it to production
agentcost analyze ./agent --events run.json --runs-per-day 2000Save a test run with local mode, where nothing leaves the process at all:
import json
from agentcost import track_costs
track_costs.init(local_mode=True)
with track_costs.workflow("support-triage"):
... # run your agent once
track_costs.flush()
json.dump(track_costs.get_local_events(), open("run.json", "w"))The report gives cost per run, the share each step contributes, a projected monthly bill at your expected volume, and findings: steps that loop, identical calls repeated inside one run, prompt files eating the context window, and duplicated content across files.
Every flag, every finding it can raise, and the CI exit codes are in the CLI reference.
This command reads your prompts and skill files, and it never transmits them. No network call is made, and no file content outlives the token count taken from it — see the privacy architecture page.
Attach custom metadata for filtering and grouping:
# Persistent metadata (attached to all subsequent events)
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 completesAgentCost supports over 3,500+ models from all major providers. Pricing is automatically synced from LiteLLM's comprehensive pricing database, ensuring you always have accurate, up-to-date cost information.
View all models: Browse the complete model catalog with search, filtering, and live pricing.
| Provider | Examples |
|---|---|
| OpenAI | gpt-4, gpt-4-turbo, gpt-4o, gpt-4o-mini, gpt-3.5-turbo, o1, o1-mini, o1-preview |
| Anthropic | claude-3-opus, claude-3-sonnet, claude-3-haiku, claude-3.5-sonnet, claude-3.5-haiku, claude-4-opus |
| gemini-pro, gemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash | |
| Groq | llama-3.1-8b, llama-3.1-70b, llama-3.3-70b, mixtral-8x7b |
| DeepSeek | deepseek-chat, deepseek-coder, deepseek-reasoner |
| Cohere | command, command-r, command-r-plus |
| Mistral | mistral-small, mistral-medium, mistral-large |
| Together AI | meta-llama/Llama-3-70b, Qwen models, Phi models |
| AWS Bedrock | All Bedrock-hosted models (Claude, Titan, Llama) |
| Azure OpenAI | All Azure-hosted OpenAI models |
| 50+ More | Replicate, Fireworks, Anyscale, Perplexity, etc. |
For custom or private models, you can provide custom pricing via the custom_pricing parameter. The SDK also fetches the latest pricing from the backend automatically.
Each tracked event contains:
{
"agent_name": "my-agent",
"model": "gpt-4",
"input_tokens": 150,
"output_tokens": 80,
"total_tokens": 230,
"cost": 0.0093,
"latency_ms": 1234,
"timestamp": "2024-01-23T10:30:45.123Z",
"success": true,
"error": null,
"streaming": false,
"metadata": {"conversation_id": "conv_456"}
}Ensure all events are sent before your application exits:
# Send pending events
track_costs.flush()
# Full shutdown
track_costs.shutdown()Tip: Use Python's atexit module to automatically call shutdown() when your application exits.
The SDK is designed to never interfere with your application. All tracking operations are:
# The SDK never throws exceptions to your code
try:
response = llm.invoke("Hello!") # This works even if tracking fails
except Exception as e:
# This will only catch LLM errors, not tracking errors
print(f"LLM error: {e}")
# To see tracking errors, enable debug mode
track_costs.init(
api_key="sk_...",
project_id="123e4567-e89b-42d3-a456-426614174000",
debug=True, # Logs errors to console
)Call track_costs.init() before creating any LLM instances:
# Correct: Initialize before importing LLM
from agentcost import track_costs
track_costs.init(
api_key="sk_...",
project_id="123e4567-e89b-42d3-a456-426614174000",
)
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4")
# Wrong: LLM created before initialization
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4")
from agentcost import track_costs
track_costs.init(
api_key="sk_...",
project_id="123e4567-e89b-42d3-a456-426614174000",
) # Too late!Context managers ensure proper agent tagging even if exceptions occur:
# Recommended: Context manager
with track_costs.agent("router"):
response = llm.invoke(query)
# Less safe: Manual setting
track_costs.set_agent_name("router")
response = llm.invoke(query) # What if this throws?
track_costs.set_agent_name("default") # Might not runStore sensitive configuration in environment variables:
import os
from agentcost import track_costs
track_costs.init(
api_key=os.environ["AGENTCOST_API_KEY"], # sk_...
project_id=os.environ["AGENTCOST_PROJECT_ID"], # project UUID
base_url=os.environ.get("AGENTCOST_URL", "https://api.agentcost.tech"),
debug=os.environ.get("DEBUG", "false").lower() == "true"
)Always flush events before your application exits:
import atexit
from agentcost import track_costs
track_costs.init(
api_key="sk_...",
project_id="123e4567-e89b-42d3-a456-426614174000",
)
# Register shutdown handler
atexit.register(track_costs.shutdown)
# Or in FastAPI/Flask
@app.on_event("shutdown")
async def shutdown_event():
track_costs.shutdown()project_id must be the project UUID from Settings, not its name — a mismatch returns 403 and the SDK logs an agentcost errortrack_costs.init() is called before LLM usagedebug=True to see error messagestrack_costs.flush() to force send eventspip install tiktokenbase_url is correctIf you're still having issues, check our GitHub Issues or start a discussion.