How interception works

The SDK patches provider client classes at init() time. Why import order does not matter, how framework calls are captured, and where the limits are.

The patch is applied to classes

capsera.init() imports each provider library that is present and replaces methods on the class:

anthropic.resources.messages.Messages.create
anthropic.resources.messages.AsyncMessages.create
openai.resources.chat.completions.Completions.create
openai.resources.embeddings.Embeddings.create

Because the patch lives on the class, import order does not matter:

client = anthropic.Anthropic()      # constructed first
capsera.init(api_key="cap-...")     # patched second

client.messages.create(...)         # recorded

client resolves create on its class at call time, and by then the class holds the wrapper. Instrumenting an application therefore does not require finding every place a client is constructed.

The limit follows from the same mechanism. A call that executed before init() ran went through the original method and is not recorded.

What the wrapper does

Around each call, in order:

  1. Resolve the calling location by walking the stack to the first frame that is not Capsera, a provider library, or a known framework.
  2. Apply routing, if enabled, which may change the model.
  3. Run the pre-call budget check, if enabled, which may raise or downgrade.
  4. Call the provider method.
  5. Read token counts from the response, compute cost, build an event, and queue it.
  6. Return the provider's response object unchanged.

Step 6 is a guarantee. For non-streaming calls the SDK does not wrap, proxy, or copy the response, so code that reads response.usage or indexes response.content behaves identically with and without instrumentation.

Framework calls need no integration

LangChain, LangGraph, CrewAI, LlamaIndex, AutoGen, Haystack, DSPy, and LiteLLM all call the provider clients listed above. Since the patch is on those classes, framework calls are recorded without any additional setup.

What frameworks affect is step 1. Without help, the first non-Capsera, non-provider frame is inside the framework, so events would report a location like langchain/…/base.py instead of your code. The SDK keeps a list of framework paths to skip so the walk continues to the function you wrote.

A wrapper library that is not on that list can produce a caller_file pointing into a dependency. The call is still recorded correctly; only the reported location is unhelpful.

Streaming is handled differently

A streamed call has no usage data when it returns. Token counts arrive with the final chunk or after the stream is drained, so the SDK returns a thin wrapper for streaming calls and records the event once the stream finishes.

This makes streaming the one case where how you consume the response affects whether the call is recorded. See Troubleshooting for the specific shapes that are not recorded. For OpenAI, the SDK also injects stream_options={"include_usage": True} so the final chunk carries token counts.

Failures are contained

Every step above is wrapped so a failure inside Capsera cannot reach your call path. If a response shape cannot be read, if a pricing lookup fails, or if the queue is full, the call still returns the provider's response. See Reliability.

init() is idempotent

init() can be called more than once. The patch is applied once per class and guarded, so a second call does not wrap twice and cannot record a call twice. Re-initialising flushes the previous emitter, so queued events are delivered rather than lost.