fix(prometheus): track spend + suppress failure counter for cancellations - #82
Merged
songkuan-zheng merged 2 commits intoJun 10, 2026
Merged
Conversation
…ions
The cancel-finalize zero-chunk / shield-timeout / no-completion paths
all route through proxy_logging_obj.post_call_failure_hook. That
dispatch reaches every callback in litellm.callbacks, but the failure
side of the integration was systemically undercounting cancel revenue:
Layer 1 — SLP construction (litellm_logging.py):
_failure_handler_helper_fn hardcoded model_call_details["response_cost"]
= 0 BEFORE building the StandardLoggingPayload. Every callback that
reads response_cost off the SLP (Prometheus litellm_spend_metric,
Langfuse generation cost, Custom Callback API body, OTel spans, etc.)
saw 0 for client cancellations even though the DB row carried a
positive spend from proxy_track_cost_callback's compute_prompt_only_cost.
Fix: detect asyncio.CancelledError and run compute_prompt_only_cost
inline (best-effort — falls back to 0 on cost-map miss or tokenizer
failure, never raises).
Layer 2 — Prometheus integration (prometheus.py):
async_log_failure_event unconditionally bumped
litellm_llm_api_failed_requests_metric and did NOT call
_increment_top_level_request_and_spend_metrics at all. Even with
Layer 1 fixed, the spend counter would still be wrong because the
failure-event path never touches it.
Fix: branch on standard_logging_payload.metadata.cancellation_indicator.
When present:
• Route SLP.response_cost to litellm_spend_metric.inc(amount=cost)
— keeps Prometheus spend in lockstep with the SpendLogs spend
column.
• SKIP litellm_llm_api_failed_requests_metric and the
set_llm_deployment_failure_metrics call — cancels are not
system failures, must not pollute failure-rate dashboards.
• Org-budget metric tracks the cancel cost too.
Otherwise (real failures) the original behaviour is preserved
exactly.
Tests:
- test_litellm_logging.py: 3 new tests covering the cancel
response_cost pre-population (positive cost for known model,
zero/no-raise on unknown model, no change for non-cancel
exceptions).
- 217/217 cancel-billing + spend_tracking + cancel_finalize tests
pass. (The one pre-existing failure in
test_logfire_logger_accepts_env_vars_for_base_url is a missing-
`opentelemetry` package on the local Python env, unaffected by
this change.)
Tier: D — refines the Phase-3 cancel taxonomy by closing a metric
gap inherited from upstream's failure-event design. Both layers are
gated on the cancellation_indicator marker so non-cancel failures
keep identical behaviour. To be folded into the same upstream issue
as PR #78's Tier-D candidate after one internal release dogfood.
End-to-end coverage for PR #82's Layer 2 routing topology. Tests the real Prometheus client state inside the running proxy (no Counter mocking, no async_log_failure_event monkey-patching — both would have been theater). Two probes against `model=mock-claude`: Probe A — streaming cancel (curl --max-time 3 mid-stream): expect: litellm_llm_api_failed_requests_metric_total delta = 0 SpendLogs row status=success + cancellation_indicator Probe B — forced X-Mock-Fail: 503 (the control): expect: litellm_llm_api_failed_requests_metric_total delta >= 1 SpendLogs row status=failure + no marker The A/B discrimination is the load-bearing assertion: both probes share the same `model` label so the counter delta is purely a function of which branch of async_log_failure_event fired. Probe B's positive delta also locks in that the fix isn't over-suppressing. Spend-VALUE verification (Layer 1's compute_prompt_only_cost populating SLP.response_cost) lives in test_litellm_logging.py::TestFailureHandlerCancelCost (uses anthropic/claude-haiku-4-5 in the cost map) and e2e/cases/data/33_real_anthropic_cancel.sh (real provider) — both land positive cost figures the mock provider can't produce because mock-claude isn't in the cost map. Wired into run-all-cases under the cancel-billing block (26 → 35). Verified end-to-end: 7/7 cancel-suite cases PASS including the new case 35.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tier classification
litellm/coreTried upstream first?
UPSTREAM_PR_QUEUE.md; will be bundled into the same upstream issue after one internal release.Summary
After PR #78 / #81, the SpendLogs DB row for a cancelled request has correct
status="success"+ cancellation_indicator + positivespend(viacompute_prompt_only_cost). But Prometheus was still divergent: cancelled requests counted as failures ANDlitellm_spend_metricundercounted.Two layers fixed in one PR:
Layer 1 —
_failure_handler_helper_fn(litellm_logging.py)response_costwas hardcoded to 0 before building the StandardLoggingPayload. Every SLP-reading callback (Prometheus, Langfuse, Custom Callback API, OTel, Helicone, …) saw 0 for cancellations even though the DB row carried a positive spend.Fix: detect
asyncio.CancelledErrorand callcompute_prompt_only_costinline. Best-effort — falls back to 0 on cost-map miss or tokenizer failure, never raises.Layer 2 —
async_log_failure_event(prometheus.py)The failure-event path didn't increment
litellm_spend_metricat all, AND it bumpedlitellm_llm_api_failed_requests_metric(which pollutes failure-rate alerting with what are really client cancellations).Fix: branch on
standard_logging_payload.metadata.cancellation_indicator. When present:response_cost(now correctly populated by Layer 1) intolitellm_spend_metricso Prometheus spend stays in lockstep with the SpendLogs spend columnlitellm_llm_api_failed_requests_metricandset_llm_deployment_failure_metrics— cancels are not system failuresNon-cancel failures: original behaviour preserved exactly (gated on the indicator).
Verification
Unit tests (Layer 1 — SLP cost population)
test_litellm_logging.py::TestFailureHandlerCancelCost:test_cancelled_error_populates_prompt_only_cost— Anthropic Haiku + 20×repeated long prompt →response_cost > 0test_cancelled_error_falls_back_to_zero_on_unknown_model— unknown model, no raise, finite resulttest_non_cancel_exception_keeps_response_cost_zero—ValueErrorkeeps 0test_logfire_logger_accepts_env_vars_for_base_urlis unrelated — missingopentelemetryoptional dep on the local Python env (confirmed reproducible BEFORE this PR viagit stash).E2E (Layer 2 — Prometheus routing topology)
e2e/cases/data/35_cancel_prometheus_metrics.shverifies the routing against the real Prometheus client state inside the running proxy (no Counter mocking — would have been theater). Two-probe discrimination:litellm_llm_api_failed_requests_metric_totaldelta = 0 (cancel suppressed) + SpendLogs row with cancellation markerX-Mock-Fail: 503control → delta ≥ 1 (real failure DOES count) + SpendLogs rowstatus=failurerun-all-casesafter case 32. Full cancel suite (26/27/28/29/30/32/35) all PASS end-to-end after the fix.Case 35 deliberately doesn't assert spend-metric VALUES because the mock provider's
mock-claudeisn't in the litellm cost map (cost=0 makesCounter.inc(0)a no-op that's indistinguishable from "Layer 2 didn't fire"). Spend-value coverage lives in:response_cost)e2e/cases/data/33_real_anthropic_cancel.sh(real provider, asserts the billed SpendLogs spend column)Impact
litellm_spend_metriccompute_prompt_only_costlitellm_llm_api_failed_requests_metriccompute_prompt_only_costresponse_costfor OTHER callbacks (Langfuse, Custom Callback API, OTel)Conflict resolutions
N/A — single commit, branch linear from
ship/v1.87.0HEAD083f325241(post-#81 merge).Pre-submission
tests/test_litellm/make test-unitequivalent passes (217/217 cancel + spend tests; one pre-existing unrelated logfire failure documented)