Skip to content

feat(prometheus): emit per-token-type detail metrics (LIT-3220) - #28372

Merged
ishaan-berri merged 4 commits into
BerriAI:litellm_shin_may20from
oss-agent-shin:feat/lit-3220-token-detail-metrics
May 20, 2026
Merged

feat(prometheus): emit per-token-type detail metrics (LIT-3220)#28372
ishaan-berri merged 4 commits into
BerriAI:litellm_shin_may20from
oss-agent-shin:feat/lit-3220-token-detail-metrics

Conversation

@oss-agent-shin

Copy link
Copy Markdown
Contributor

Summary

Adds five sparse Prometheus counter metrics that break out the token detail fields providers already report in usage.prompt_tokens_details and usage.completion_tokens_details:

Metric Source
litellm_input_cached_tokens_metric prompt_tokens_details.cached_tokens (OpenAI prompt cache, Anthropic cache_read_input_tokens)
litellm_input_cache_creation_tokens_metric prompt_tokens_details.cache_creation_tokens (Anthropic cache_creation_input_tokens)
litellm_input_audio_tokens_metric prompt_tokens_details.audio_tokens
litellm_output_reasoning_tokens_metric completion_tokens_details.reasoning_tokens
litellm_output_audio_tokens_metric completion_tokens_details.audio_tokens

No breaking changes — existing litellm_input_tokens_metric / litellm_output_tokens_metric / litellm_total_tokens_metric are untouched. The new counters are additive and only emit when the underlying value is > 0, keeping scrape output sparse for providers that don't report a given field. Each new counter reuses the same label set as its parent input/output token counter, so dashboards can join cleanly.

Data is read from the canonical Usage dict that get_standard_logging_object_payload already attaches at standard_logging_payload["metadata"]["usage_object"] — no new plumbing through the logging pipeline.

Closes LIT-3220.

Visual proof — /metrics output after driving both an Anthropic-shaped and an OpenAI-shaped Usage payload

For the Anthropic call (cached_tokens=800, cache_creation_tokens=200, no audio/reasoning) and the OpenAI audio call (cached_tokens=100, audio_in=50, reasoning=220, audio_out=40), the new counters appear with the expected values and stay sparse for fields the provider didn't report (no cache_creation line for the OpenAI call, no audio/reasoning lines for the Anthropic call):

/metrics output

The same artifact is committed at proof/lit-3220-metrics.png on this branch.

Test plan

  • 10 new unit tests in tests/test_litellm/integrations/test_prometheus_token_detail_metrics.py covering registration, label-set parity, all-types increment, and zero/None/negative/missing-metadata skip paths
  • Existing test_prometheus_cache_metrics.py / test_prometheus_labels.py / test_prometheus_metric_name_consistency.py continue to pass (33/33 green)
  • uv run black . clean
  • End-to-end smoke: drove PrometheusLogger._increment_token_metrics with Anthropic- and OpenAI-shaped payloads, scraped via prometheus_client.generate_latest(), confirmed correct values + sparse behavior (see screenshot)

🤖 Generated with Claude Code

shin-berri and others added 4 commits May 13, 2026 22:37
chore(ci): promote internal staging to main
Adds five sparse counter metrics that break out the token detail
fields providers already report in `usage.prompt_tokens_details` and
`usage.completion_tokens_details`:

  - litellm_input_cached_tokens_metric            (provider prompt-cache reads)
  - litellm_input_cache_creation_tokens_metric    (Anthropic prompt-cache writes)
  - litellm_input_audio_tokens_metric             (audio input tokens)
  - litellm_output_reasoning_tokens_metric        (reasoning tokens)
  - litellm_output_audio_tokens_metric            (audio output tokens)

These are additive — existing input/output/total counters are
unchanged, so no dashboards break. Each new counter is only
incremented when the underlying detail is populated and > 0, keeping
scrape output sparse for providers that don't report a given field.

Data is read from the canonical Usage dict that
`get_standard_logging_object_payload` already attaches at
`standard_logging_payload["metadata"]["usage_object"]`, so no new
plumbing through the logging pipeline is required.

Tests: 10 new unit tests covering registration, label-set parity,
all-types increment, zero/None/negative skip behaviour, and the
no-metadata/no-usage_object no-op paths.

Closes LIT-3220

Co-Authored-By: Claude <noreply@anthropic.com>
@CLAassistant

CLAassistant commented May 20, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 3 committers have signed the CLA.

✅ yuneng-berri
❌ shin-berri
❌ krrish-berri
You have signed the CLA already but the status is still pending? Let us recheck it.

@codspeed-hq

codspeed-hq Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing oss-agent-shin:feat/lit-3220-token-detail-metrics (4167491) with main (79b4578)

Open in CodSpeed

@codecov

codecov Bot commented May 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.65217% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/integrations/prometheus.py 94.44% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@ishaan-berri
ishaan-berri changed the base branch from main to litellm_internal_staging May 20, 2026 18:26
@greptile-apps

greptile-apps Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds five sparse Prometheus counters (litellm_input_cached_tokens_metric, litellm_input_cache_creation_tokens_metric, litellm_input_audio_tokens_metric, litellm_output_reasoning_tokens_metric, litellm_output_audio_tokens_metric) that break out per-token-type detail fields already present in usage.prompt_tokens_details / usage.completion_tokens_details. The change is fully additive — existing total-token counters are untouched and the new counters are only emitted when the underlying value is > 0.

  • Five new counters registered in PrometheusLogger.__init__ and incremented via the new _increment_token_detail_metrics helper, which reads from standard_logging_payload[\"metadata\"][\"usage_object\"] — no new plumbing in the hot path.
  • Label sets for the new metrics alias the existing litellm_input_tokens_metric / litellm_output_tokens_metric label lists so dashboards can join across them.
  • Ten new unit tests cover registration, label-set parity, all-types increment, and zero/None/negative/missing-metadata skip paths; all tests use mocks and make no network calls.

Confidence Score: 4/5

This is a purely additive change with no modifications to existing counters or the request hot path; it is safe to merge.

The new helper reads from a pre-existing metadata field, all counters are sparse (only emitted when the value is > 0), and existing token metrics are unchanged. The isinstance(…, dict) guards inside the detail-metrics list are technically reachable only when a Pydantic wrapper object bypasses the or {} fallback, which silently drops the metric rather than causing an error. The binary PNG committed to the source tree is a process concern with no runtime impact.

The proof/lit-3220-metrics.png binary should not live in the main repo; the isinstance(prompt_details, dict) guards in _increment_token_detail_metrics warrant a second look for the Pydantic-object edge case.

Important Files Changed

Filename Overview
litellm/integrations/prometheus.py Registers five new sparse Prometheus counters for per-token-type detail metrics and adds _increment_token_detail_metrics; the implementation is clean and follows the existing _inc_labeled_counter pattern correctly.
litellm/types/integrations/prometheus.py Adds five new metric names to the DEFINED_PROMETHEUS_METRICS Literal and assigns label sets in PrometheusMetricLabels by aliasing the existing input/output label lists; straightforward and consistent with the existing pattern.
tests/test_litellm/integrations/test_prometheus_token_detail_metrics.py New unit test file with 10 mock-only tests covering registration, label parity, zero/None/negative skip paths, and full increment; tests correctly bind to the real _increment_token_detail_metrics implementation via unbound method call.
proof/lit-3220-metrics.png Binary screenshot committed into the main repo; documentation artifacts like this belong in the litellm-docs repo or hosted as PR attachments, not in the source tree.

Comments Outside Diff (1)

  1. proof/lit-3220-metrics.png, line 1 (link)

    P2 Binary proof artifact committed to main repo

    Per the team's documentation guidelines, artifacts like screenshots and visual proof should live in the litellm-docs repo or be attached directly to the PR (GitHub renders images pasted or dragged into PR descriptions without requiring a committed file). Committing a binary PNG to the source tree inflates repo clone size for every developer and CI runner going forward, with no runtime benefit.

    Rule Used: Prevent documentation from being added - needs to ... (source)

Reviews (1): Last reviewed commit: "feat(prometheus): emit per-token-type de..." | Re-trigger Greptile

Comment on lines +1383 to +1384
prompt_details = usage_object.get("prompt_tokens_details") or {}
completion_details = usage_object.get("completion_tokens_details") or {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 isinstance(…, dict) guards are unreachable after the or {} fallback

prompt_details and completion_details are assigned via … or {}, which means they are either a plain dict (from model_dump() or JSON parsing) or a truthy non-dict value such as a PromptTokensDetailsWrapper / CompletionTokensDetailsWrapper Pydantic object. In the Pydantic-object case isinstance(…, dict) returns False and every field silently resolves to None, so no detail metric fires — even though both wrapper classes expose a compatible .get() method. Using .get() directly (which works for both dicts and the wrapper models) after the or {} guard would be both simpler and more defensive.

@ishaan-berri
ishaan-berri changed the base branch from litellm_internal_staging to litellm_shin_may20 May 20, 2026 18:37
@ishaan-berri
ishaan-berri merged commit 31b1d96 into BerriAI:litellm_shin_may20 May 20, 2026
50 of 51 checks passed
ishaan-berri pushed a commit to BerriAI/litellm-docs that referenced this pull request May 20, 2026
Adds a "Token type detail metrics" subsection to docs/proxy/prometheus.md
covering the five new sparse counters added in
BerriAI/litellm#28372 (LIT-3220):

  - litellm_input_cached_tokens_metric
  - litellm_input_cache_creation_tokens_metric
  - litellm_input_audio_tokens_metric
  - litellm_output_reasoning_tokens_metric
  - litellm_output_audio_tokens_metric

Includes:
  - Source field on the Usage object for each metric
  - Typical providers that populate each field
  - PromQL examples for cache-hit ratio and reasoning-token share
  - Note distinguishing provider-side cached input tokens from
    LiteLLM's own response-cache `litellm_cached_tokens_metric`

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
ishaan-berri added a commit that referenced this pull request May 23, 2026
…) (#28378)

* feat(prometheus): emit per-token-type detail metrics (LIT-3220) (#28372)

Adds five sparse counter metrics that break out the token detail
fields providers already report in `usage.prompt_tokens_details` and
`usage.completion_tokens_details`:

  - litellm_input_cached_tokens_metric            (provider prompt-cache reads)
  - litellm_input_cache_creation_tokens_metric    (Anthropic prompt-cache writes)
  - litellm_input_audio_tokens_metric             (audio input tokens)
  - litellm_output_reasoning_tokens_metric        (reasoning tokens)
  - litellm_output_audio_tokens_metric            (audio output tokens)

These are additive — existing input/output/total counters are
unchanged, so no dashboards break. Each new counter is only
incremented when the underlying detail is populated and > 0, keeping
scrape output sparse for providers that don't report a given field.

Data is read from the canonical Usage dict that
`get_standard_logging_object_payload` already attaches at
`standard_logging_payload["metadata"]["usage_object"]`, so no new
plumbing through the logging pipeline is required.

Tests: 10 new unit tests covering registration, label-set parity,
all-types increment, zero/None/negative skip behaviour, and the
no-metadata/no-usage_object no-op paths.

Closes LIT-3220

Co-authored-by: shin-berri <shin-laptop@berri.ai>
Co-authored-by: yuneng-jiang <yuneng@berri.ai>
Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove proof folder image

---------

Co-authored-by: oss-agent-shin <ext-agent-shin@berri.ai>
Co-authored-by: shin-berri <shin-laptop@berri.ai>
Co-authored-by: yuneng-jiang <yuneng@berri.ai>
Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ishaan Jaffer <ishaanjaffer0324@gmail.com>
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…iAI#28372) (BerriAI#28378)

* feat(prometheus): emit per-token-type detail metrics (LIT-3220) (BerriAI#28372)

Adds five sparse counter metrics that break out the token detail
fields providers already report in `usage.prompt_tokens_details` and
`usage.completion_tokens_details`:

  - litellm_input_cached_tokens_metric            (provider prompt-cache reads)
  - litellm_input_cache_creation_tokens_metric    (Anthropic prompt-cache writes)
  - litellm_input_audio_tokens_metric             (audio input tokens)
  - litellm_output_reasoning_tokens_metric        (reasoning tokens)
  - litellm_output_audio_tokens_metric            (audio output tokens)

These are additive — existing input/output/total counters are
unchanged, so no dashboards break. Each new counter is only
incremented when the underlying detail is populated and > 0, keeping
scrape output sparse for providers that don't report a given field.

Data is read from the canonical Usage dict that
`get_standard_logging_object_payload` already attaches at
`standard_logging_payload["metadata"]["usage_object"]`, so no new
plumbing through the logging pipeline is required.

Tests: 10 new unit tests covering registration, label-set parity,
all-types increment, zero/None/negative skip behaviour, and the
no-metadata/no-usage_object no-op paths.

Closes LIT-3220

Co-authored-by: shin-berri <shin-laptop@berri.ai>
Co-authored-by: yuneng-jiang <yuneng@berri.ai>
Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>

* chore: remove proof folder image

---------

Co-authored-by: oss-agent-shin <ext-agent-shin@berri.ai>
Co-authored-by: shin-berri <shin-laptop@berri.ai>
Co-authored-by: yuneng-jiang <yuneng@berri.ai>
Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Ishaan Jaffer <ishaanjaffer0324@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants