Integrations
Four ways to get spans flowing, from zero-code to fully manual. All produce the same typed cost graph and compose freely inside one trace.
| Integration | Code per call site | Covers |
|---|---|---|
| LangChain / LangGraph | 0 lines (one callback) | chains, graphs, tools, retrievers |
| LiteLLM | 0 lines (one callback) | 100+ providers via SDK or proxy |
| Client wrappers | 0 lines (wrap once) | direct OpenAI / Anthropic SDK use |
| Manual | 2 lines | anything else |
LangChain / LangGraph
pip install "stepcost[langchain]"
from stepcost import StepCost
from stepcost.integrations.langchain import StepCostCallbackHandler
cc = StepCost(project="my-app", sink="sqlite:///~/.stepcost/my-app.db")
handler = StepCostCallbackHandler(cc)
with cc.trace(feature_id="support-bot", customer_id="acme") as trace:
agent.invoke(inputs, config={"callbacks": [handler]})
Every chain/graph node becomes an agent_step, model calls become priced
llm_generation spans, tools and retrievers are captured. Parenting follows
LangChain's own run tree, so parallel branches attribute correctly; LCEL
plumbing (RunnableSequence etc.) is filtered out of the tree.
usage_metadata.input_tokens.
The handler unfolds them, so cached tokens are billed at the cache rate — not double-counted
at the full input rate.
LiteLLM
No extra dependency — LiteLLM accepts plain callables:
import litellm
from stepcost.integrations.litellm import StepCostLiteLLMLogger
logger = StepCostLiteLLMLogger(cc)
litellm.success_callback = [logger.log_success_event]
litellm.failure_callback = [logger.log_failure_event]
with cc.trace(feature_id="chat") as trace:
litellm.completion(model="claude-haiku-4-5", messages=[...])
Handles LiteLLM's normalized usage for both OpenAI-style (cached subset subtraction) and Anthropic-style (cache read/write counters) responses, and records call duration. Because the LiteLLM proxy sees traffic from every language, this is also the cheapest way to cover non-Python services.
OpenAI / Anthropic client wrappers
Wrap the official SDK clients once; every call is priced automatically:
from stepcost.integrations.openai import instrument_openai
from stepcost.integrations.anthropic import instrument_anthropic
openai_client = instrument_openai(OpenAI(), cc) # chat, responses, embeddings
claude = instrument_anthropic(anthropic.Anthropic(), cc) # messages
with cc.trace(feature_id="chat") as trace:
openai_client.chat.completions.create(model="gpt-4o-mini", messages=[...])
claude.messages.create(model="claude-haiku-4-5", max_tokens=64, messages=[...])
- Spans nest under whatever
agent_stepis open at the call site. - Provider errors re-raise untouched but still close the span with an
errortag. - Streaming responses (no usage object) record
usage=unavailableinstead of guessing.
Manual context managers
For custom providers, tool calls, and retrieval steps:
from stepcost import agent_step, llm_call
with cc.trace(feature_id="pipeline") as trace:
with agent_step("summarize"):
with llm_call(model="my-model", provider="other") as call:
resp = my_client.generate(...)
call.record_usage(input_tokens=resp.in_toks, output_tokens=resp.out_toks)
Unknown models warn loudly and show as "unpriced spans" in stepcost report —
add them with register_custom_model() or a custom price table
(Concepts & pricing).