Python SDK
SDK Documentation
Complete guide to integrating AgentCost into your OpenAI, Anthropic, Gemini, and LangChain applications
Installation
Install the AgentCost SDK using pip:
pip install agentcostOr install from source:
cd agentcost-sdk
pip install -e .Quick Start
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.
Verify it worked
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.
Configuration
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"
}
)Configuration Options
| 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 |
Agent Tagging
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.
Workflows & Steps
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.
Tool names also feed the Guardrails page: declare which tools an agent may call (and whether it is read-only), which models it may use, and how many tool calls or how much cost a single workflow() run may reach, and observed usage is judged against that boundary. This is deliberately separate from success rate — a failed call on a permitted tool is not a breach, and a successful call on a forbidden one is. From SDK 0.2.2, tool() records the tool name with or without a workflow around it; only uninstrumented calls are invisible, so the page reports coverage next to every verdict.
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.
External Correlation
A process that wraps your agent — a policy layer, an orchestrator, a CI job — can join its own records to AgentCost cost data by exporting one variable. No code change in the agent: the SDK it already runs picks the id up from the environment and stamps it on every event.
export AGENTCOST_TRACE_ID=0532f9c4-a022-4e98-a543-d8e17c5b90a6
export AGENTCOST_WORKFLOW=refactor-run # optional, names the runPrecedence is always: an explicit workflow("name", trace_id=...) argument, then an active workflow(), then the environment. Trace ids accept up to 64 characters, so UUIDs and ULIDs fit. Read the joined run back with GET /v1/analytics/traces/{trace_id}, and report how it ended — even with no events attached — via outcomes on the batch endpoint.
Pre-deployment Analysis
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.
Metadata
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")Local Mode
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 Support
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 completesSupported Models
AgentCost 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.
Event Structure
Each tracked event contains:
{
"agent_name": "my-agent",
"model": "gpt-4o",
"input_tokens": 1500,
"output_tokens": 80,
"total_tokens": 1580,
"cached_tokens": 1200,
"cost": 0.0031,
"latency_ms": 1234,
"timestamp": "2026-08-15T10:30:45.123Z",
"success": true,
"error": null,
"streaming": false,
"metadata": {"conversation_id": "conv_456"}
}Prompt-cache accounting: cached_tokens is the part of the prompt served from the provider's cache, read automatically off OpenAI, Anthropic and Gemini responses (Anthropic cache writes are reported separately as cache_write_tokens, since they bill at a premium). Cached tokens are priced at the provider's real cache-read rate — on cache-heavy agent workloads this is the difference between the right bill and one overstated several times over.
Graceful Shutdown
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.
Error Handling
The SDK is designed to never interfere with your application. All tracking operations are:
- Non-blocking: Events are batched and sent asynchronously
- Fault-tolerant: Network failures are silently handled
- Retry-enabled: Failed batches are retried with exponential backoff
# 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
)Best Practices
1. Initialize Early
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!2. Use Agent Context Managers
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 run3. Environment Variables
Store 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"
)4. Graceful Shutdown
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()Troubleshooting
Events not appearing in dashboard
- #1 cause:
project_idmust be the project UUID from Settings, not its name — a mismatch returns 403 and the SDK logs anagentcosterror - Ensure
track_costs.init()is called before LLM usage - Check your API key is correct
- Enable
debug=Trueto see error messages - Call
track_costs.flush()to force send events
Token counts seem wrong
- The SDK uses tiktoken for accurate counting
- Make sure tiktoken is installed:
pip install tiktoken - Some models may use different tokenizers
Connection errors
- Verify your
base_urlis correct - Check that the backend is running and accessible
- Look for firewall or proxy issues
Getting support
If you're still having issues, check our GitHub Issues or start a discussion.