Skip to content

Supported Usage Types

Usage.from_openai() accepts every usage object below. Each is normalized into the same Usage shape with requests=1.

OpenAI source Type passed to from_openai What is captured
Chat Completions openai.types.completion_usage.CompletionUsage input/output tokens, cached, reasoning
Responses API openai.types.responses.ResponseUsage input/output tokens, cached, reasoning
Agents SDK agents.Usage input/output tokens, cached, reasoning
Agents SDK (context) agents.RunContextWrapper unwrapped to its .usage
Realtime openai.types.realtime.RealtimeResponseUsage input/output tokens, cached, audio, text
Batch openai.types.batch_usage.BatchUsage input/output tokens, cached, reasoning
Embeddings openai.types.create_embedding_response.Usage input tokens only (no output)
Images (gpt-image-1) openai.types.images_response.Usage input/output tokens, image, text
Transcription (tokens) openai.types.audio.transcription.UsageTokens input/output tokens, audio, text
Transcription (duration) openai.types.audio.transcription.UsageDuration seconds only
Assistants run openai.types.beta.threads.run.Usage prompt/completion tokens
Assistants run step openai.types.beta.threads.runs.run_step.Usage prompt/completion tokens

Other providers

Two non-OpenAI SDKs are supported through the same from_openai() call, each behind an optional extra:

Source Type passed to from_openai What is captured
Gemini google.genai.types.GenerateContentResponseUsageMetadata input/output tokens, cached, thinking, tool-use prompt, per-modality audio/image/video/text
LiteLLM litellm.types.utils.Usage input/output tokens, cached, cache-write, reasoning, audio/image/video/text, cost

Neither SDK is imported unless you already imported it yourself, so installing the extras is only necessary if you actually pass their objects.

# Gemini
response = client.models.generate_content(model="gemini-2.5-pro", contents="hi")
usage = Usage.from_openai(response.usage_metadata)

# LiteLLM
response = litellm.completion(model="claude-sonnet-4-5", messages=messages)
usage = Usage.from_openai(response.usage)

Two mapping decisions are worth knowing about:

  • Gemini reports thoughts_token_count separately from candidates_token_count. output_tokens is the sum of both, and reasoning_tokens holds the thinking count.
  • Gemini's tool_use_prompt_token_count is added to input_tokens, since it is billed as input.

All the OpenAI types are also re-exported from the package root for convenience and type hints:

from openai_usage import (
    BatchUsage,
    EmbeddingUsage,
    ImagesUsage,
    RealtimeResponseUsage,
    RunStepUsage,
    RunUsage,
    TranscriptionUsageDuration,
    TranscriptionUsageTokens,
    AnyUsage,     # the Union of everything from_openai accepts
    OpenAIUsage,  # the OpenAI-only subset
)

Examples by modality

Realtime (audio)

from openai_usage import Usage

# event is a `response.done` event from a realtime session
usage = Usage.from_openai(event.response.usage)

print(usage.input_tokens_details.audio_tokens)
print(usage.output_tokens_details.audio_tokens)

Embeddings

response = client.embeddings.create(model="text-embedding-3-small", input="hello")
usage = Usage.from_openai(response.usage)

assert usage.output_tokens == 0   # embeddings have no generated output

Image generation

result = client.images.generate(model="gpt-image-1", prompt="a red bicycle")
usage = Usage.from_openai(result.usage)

print(usage.input_tokens_details.image_tokens)
print(usage.output_tokens_details.image_tokens)

Transcription

from openai_usage import Usage

# gpt-4o-transcribe → billed by tokens
usage = Usage.from_openai(transcription.usage)        # UsageTokens
print(usage.input_tokens_details.audio_tokens)

# whisper-1 → billed by duration
usage = Usage.from_openai(transcription.usage)        # UsageDuration
print(usage.seconds)

Not LLM usage? Not included.

OpenAI's organization-level Usage API (/organization/usage/*) reports aggregated billing buckets rather than per-call token counts, so it is intentionally out of scope for this library.