Skip to content

feat(prometheus): add call_type label to request lifecycle metrics - #34717

Open
Bungic wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
Bungic:prometheus-call-type-label
Open

feat(prometheus): add call_type label to request lifecycle metrics#34717
Bungic wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
Bungic:prometheus-call-type-label

Conversation

@Bungic

@Bungic Bungic commented Jul 26, 2026

Copy link
Copy Markdown

TLDR

Problem this solves:

  • chat, embedding, image and audio traffic all share one litellm_requests_metric and one litellm_spend_metric series
  • embeddings are usually far higher volume and far lower cost than chat, so both the request rate and the spend numbers are hard to read once they are mixed

How it solves it:

  • adds a call_type label to the 16 request lifecycle metrics
  • collapses async call types onto their sync twin so acompletion and completion do not become two series

The value is already on standard_logging_payload as call_type, so this is label plumbing rather than new collection

Same shape as #32126, which added api_provider to the metrics that were emitted from call sites already holding the value. Part of #34704

The async spelling

CallTypes names every async variant after its sync one, and the proxy is async while the SDK usually is not. Left alone, one proxy's chat traffic would be labelled acompletion and an SDK user's would be completion, which splits the same operation across two series

The alias table is derived from CallTypes at import time, so a new call type is covered without editing the integration. It matches on member names rather than values on purpose: stripping a leading a from the value turns add_message into dd_message and anthropic_messages into nthropic_messages, and both of those are sync call types that legitimately start with one. There is a test for each

Relevant issues

Part of #34704

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Screenshots / Proof of Fix

Two deployments on a local proxy, one chat and one embedding. The deployments use mock_response so the run costs nothing; the label is read off standard_logging_payload, so the provider's answer has no bearing on it

model_list:
  - model_name: chat-demo
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY
      mock_response: "hi"
  - model_name: embed-demo
    litellm_params:
      model: openai/text-embedding-3-small
      api_key: os.environ/OPENAI_API_KEY
      mock_response: "[0.1, 0.2]"

litellm_settings:
  callbacks: ["prometheus"]

general_settings:
  master_key: sk-1234

Three chat calls and two embedding calls, then scrape:

for i in 1 2 3; do
  curl -s -o /dev/null http://localhost:4000/v1/chat/completions \
    -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
    -d '{"model":"chat-demo","messages":[{"role":"user","content":"hi"}]}'
done
for i in 1 2; do
  curl -s -o /dev/null http://localhost:4000/v1/embeddings \
    -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
    -d '{"model":"embed-demo","input":"hello"}'
done

curl -sL -H "Authorization: Bearer sk-1234" http://localhost:4000/metrics \
  | grep "^litellm_requests_metric_total{"

After, with the labels trimmed to the interesting ones:

call_type=completion   model=gpt-4o                   requested_model=chat-demo  -> 3.0
call_type=embedding    model=text-embedding-3-small   requested_model=embed-demo -> 2.0

Before, both rows collapse into series that differ only by model name, with nothing to group or filter on. Note the proxy served these asynchronously and the label still reads completion, which is the normalisation doing its job

Type

🆕 New Feature

Changes

litellm/types/integrations/prometheus.py: UserAPIKeyLabelNames.CALL_TYPE, a call_type field on UserAPIKeyLabelValues, and the label added to 16 metric label lists

litellm/integrations/prometheus.py: _build_async_call_type_aliases() builds the async to sync table from CallTypes at import time, _normalize_call_type() applies it, and the value is populated at the three sites that already resolve the payload: async_log_success_event, set_llm_deployment_failure_metrics and async_post_call_failure_hook

tests/test_litellm/integrations/test_prometheus_labels.py: extended with allow-list assertions, the normalisation table, a check that every async member of CallTypes is covered, and an end to end assertion that reads the label back off a rendered sample

Remaining quota gauges and the configured tpm/rpm limits deliberately do not get the label, since headroom belongs to a deployment rather than to a call type; splitting it would emit several series each claiming to describe the same number. There is a test pinning that

QA runbook

  1. start a proxy with the config above
  2. send a few chat completions to chat-demo and a few embeddings to embed-demo
  3. curl -sL -H "Authorization: Bearer sk-1234" http://localhost:4000/metrics | grep "^litellm_requests_metric_total{" should show two series, one call_type="completion" and one call_type="embedding"
  4. on unpatched code neither series carries the label

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds normalized call_type labels to Prometheus request lifecycle metrics.

  • Derives async-to-sync call-type aliases from the CallTypes enum.
  • Populates normalized call types on success and failure metric paths.
  • Extends metric label definitions and tests label inclusion, normalization, exclusions, and rendered output.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
litellm/integrations/prometheus.py Normalizes async call types and supplies the resulting label at success, proxy-failure, and deployment-failure population sites.
litellm/types/integrations/prometheus.py Adds the call_type label and value field to the intended request lifecycle metric definitions while excluding deployment and quota gauges.
tests/test_litellm/integrations/test_prometheus_labels.py Adds coverage for metric allow-lists, async normalization, enum coverage, gauge exclusions, and rendered Prometheus output.

Reviews (2): Last reviewed commit: "feat(prometheus): add call_type label to..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing Bungic:prometheus-call-type-label (041ce5f) with litellm_internal_staging (2412326)

Open in CodSpeed

Chat completions, embeddings, image generation and audio all land in the same
litellm_requests_metric and litellm_spend_metric series today. Embedding traffic
is usually orders of magnitude higher volume and orders of magnitude lower cost
than chat, so mixing them makes both the request rate and the spend series hard
to reason about. The value is already on standard_logging_payload as call_type,
so this is label plumbing rather than new collection.

Async and sync entry points report different call types for the same operation,
acompletion against completion, and the proxy is async while the SDK usually is
not. Emitting both spellings would split every proxy's chat traffic in two, so
each async call type is collapsed onto its sync twin. The alias table is derived
from CallTypes at import time and matches on member names rather than values:
stripping a leading "a" from the value would turn add_message into dd_message
and anthropic_messages into nthropic_messages, both of which are sync call types
that legitimately start with one.

16 metrics gain the label. Remaining-quota gauges and the configured tpm/rpm
limits deliberately do not, since headroom belongs to a deployment rather than
to a call type and splitting it would emit several series each claiming to
describe the same number. A test pins that exclusion.

Same shape as BerriAI#32126, which added api_provider to the metrics emitted from call
sites that already held the value.
@Bungic
Bungic force-pushed the prometheus-call-type-label branch from 4d70641 to 041ce5f Compare July 26, 2026 15:11
@Bungic

Bungic commented Jul 26, 2026

Copy link
Copy Markdown
Author

@greptileai

Re-requesting after a push. The only change since the last review is two type annotations in litellm/integrations/prometheus.py, switched to the modern spelling so the strict-rule budget gate in lint stays within its ceiling: Dict[str, str] to dict[str, str], and Optional[str] to str | None on _normalize_call_type. No behaviour change.

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.

1 participant