OpenAI
Chat completions and embeddings across sync, async, and streaming. Why the SDK injects include_usage, and the limitations to know.
Fully covered for chat completions and embeddings, and verified against the live API on
every release, including across the openai 1.x to 2.x major version change.
pip install openai capsera
import openai
import capsera
capsera.init(api_key=os.environ["CAPSERA_API_KEY"])
client = openai.OpenAI()
Coverage
| Call | Recorded |
|---|---|
client.chat.completions.create(...) | Yes |
await async_client.chat.completions.create(...) | Yes |
create(..., stream=True), sync and async | Yes |
client.embeddings.create(...), sync and async | Yes |
| provider errors on any of the above | Yes |
client.responses.create(...) | No |
legacy client.completions.create(...) | No |
For the Responses API, use record().
Streaming and include_usage
A streamed response carries no usage data unless requested, so the SDK injects
stream_options={"include_usage": True} into the request. Without it there would be
nothing to record, and estimating tokens from the text would put a guess in a cost
column.
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Summarise this ticket."}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")
Your own stream_options are preserved. The injection adds include_usage alongside
them rather than replacing them.
The extra final chunk carrying usage has empty choices, so guard a loop that indexes
choices[0]:
for chunk in stream:
if chunk.choices: # the usage chunk has none
print(chunk.choices[0].delta.content or "", end="")
Two streaming limitations. A stream abandoned before the end has no usage chunk and is not recorded. Streamed calls record cache tokens as zero, while the non-streaming path reports them correctly.
Embeddings
Recorded and costed, sync and async:
client.embeddings.create(model="text-embedding-3-small", input=chunks)
Embeddings bypass routing, budget enforcement, and prompt analysis. A blocking budget will not stop one and a routing rule will not redirect one. This matters most for indexing jobs, where embeddings are often the dominant cost.
Azure OpenAI
Recorded through the same patch, and labelled separately. The SDK reads the client's
base_url and sets the provider to azure rather than openai, so Azure spend does not
merge into direct OpenAI spend.
client = openai.AzureOpenAI(azure_endpoint=..., api_key=..., api_version=...)
Other OpenAI-compatible vendors
Groq, DeepSeek, xAI, Together, Fireworks, Perplexity, OpenRouter, Ollama, and vLLM all use this client and are all recorded, each labelled by its base URL. See OpenAI-compatible providers.
Errors
Recorded as zero-cost events carrying the exception type, such as RateLimitError,
BadRequestError, or APIStatusError, then re-raised unchanged. Messages are not
recorded, because they often quote the prompt.