Skip to content

fix(proxy): estimate auto-router baseline costs from durable cache history - #41177

Merged
tin-berri merged 1 commit into
mainfrom
litellm_autorouter_baseline_cache
Sep 19, 2026
Merged

tin-berri merged 1 commit into
mainfrom
litellm_autorouter_baseline_cache

Conversation

@tin-berri

@tin-berri tin-berri commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Expired prefixes can produce false reported losses
  • Zero savings must retain both nonzero costs
  • Unknown estimates must not distort savings percentages

How it solves it:

  • Match cache prefixes, availability times and TTLs
  • Preserve observed costs before routing diverges
  • Publish replayable estimates with explicit coverage

User Flow

Before: a conversation can report losses from unsupported baseline cache reads

  1. Send POST http://localhost:47998/v1/messages through the auto-router with a session ID and cache controls
  2. Continue after changing the cached prefix or letting it expire
  3. Read the session costs: the changed prefix reports −$0.05672550, and expiry reports −$0.03224500

After: each comparison requires observed costs or usable prefix history

  1. Send POST http://localhost:47999/v1/messages through the auto-router with a session ID and cache controls
  2. Continue after changing the cached prefix or letting it expire
  3. Read the session costs: baseline-identical turns preserve both costs and $0 savings; the expired five-minute prefix reports +$0.06131975, while unavailable one-hour history produces an unknown comparison

Relevant issues

Changes

  • Capture observations without pre-dispatch database or token-count calls
  • Replay late observations and atomically correct request, session and daily estimates
  • Preserve actual billed spend while affected estimates await replay
  • Carry covered actual/baseline costs through the API, dashboard and CLI
  • Preserve upstream Azure metadata and logging changes during rebase

Affected release

Linear ticket

Pre-Submission checklist

  • Focused Python checks: 1,038 passed, including real PostgreSQL
  • Dashboard checks: 67 passed; TypeScript check passed
  • Full make check, static scanners and cost-map guard passed
  • Scope remains baseline estimation; provider pricing shipped separately
  • Current-tip hosted CI, final coverage and reviewer checks complete

Screenshots / Proof of Fix

Fresh paid native-message requests on the current base and tip, with isolated PostgreSQL, unique prefixes and uncached suffixes. Every streamed request ended with message_stop

The final revision only removes misplaced product documentation; runtime and test files are byte-identical to the locally tested e103981. The requests below were rerun after that revision. The expiry case resumes a session seeded on the identical runtime before the documentation move, after more than five minutes

First-turn equality and cold-write pricing after the five-minute window passed. Gateway token counting exceeded the three-second deadline on seed requests, so the one-hour cases remain unknown. This run does not establish a successful live warm-prefix hit; the matching-prefix cases pass controlled regression tests

Shared setup and reproducible requests

Run each revision with the same isolated PostgreSQL database, gateway credentials and model configuration. The before proxy uses port 47998; after uses 47999. Apply the schema migrations before starting after

model_list:
  - model_name: sonnet
    litellm_params:
      model: anthropic/claude-sonnet-5
      api_key: os.environ/BASELINE_GATEWAY_KEY
      api_base: os.environ/BASELINE_GATEWAY_URL
  - model_name: opus
    litellm_params:
      model: anthropic/claude-opus-5
      api_key: os.environ/BASELINE_GATEWAY_KEY
      api_base: os.environ/BASELINE_GATEWAY_URL
  - model_name: baseline-cache-router
    litellm_params:
      model: auto_router/complexity_router
      complexity_router_config:
        tiers: {SIMPLE: [sonnet], MEDIUM: [sonnet], COMPLEX: [opus], REASONING: [opus]}
        classifier_type: heuristic
        classification_mode: every_request
        session_affinity: false
        keyword_tier_rules:
          - {keywords: [USE_OPUS], tier: COMPLEX}
          - {keywords: [USE_SONNET], tier: SIMPLE}
general_settings:
  master_key: os.environ/LOCAL_PROXY_KEY
  database_url: os.environ/DATABASE_URL
router_settings:
  num_retries: 0

Generate a fresh payload for each side and TTL. Keep the session ID and cached block unchanged for the follow-up; append an assistant turn and a user turn requesting USE_SONNET. For the changed-prefix case, prepend a fresh UUID inside the cached block. For five-minute expiry, wait at least 320 seconds before sending the follow-up

import json, uuid
run = uuid.uuid4().hex
ttl = "1h"  # repeat with "5m"
body = {
    "model": "baseline-cache-router", "litellm_session_id": run,
    "stream": True, "max_tokens": 16,
    "messages": [{"role": "user", "content": [
        {"type": "text", "text": run + "\n" +
         "A stable example for comparing recorded request cache costs.\n" * 900,
         "cache_control": {"type": "ephemeral", "ttl": ttl}},
        {"type": "text", "text": "USE_OPUS. Reply only OK. " + run}
    ]}]
}
open("request.json", "w").write(json.dumps(body))
curl -sS -N "$PROXY_URL/v1/messages"   -H "Authorization: Bearer $LOCAL_PROXY_KEY"   -H 'Content-Type: application/json' -H 'anthropic-version: 2023-06-01'   --data-binary @request.json
curl -sS "$PROXY_URL/spend/logs?request_id=$REQUEST_ID"   -H "Authorization: Bearer $LOCAL_PROXY_KEY"
curl -sS "$PROXY_URL/auto_router/session?session_id=$SESSION_ID"   -H "Authorization: Bearer $LOCAL_PROXY_KEY"

Allow asynchronous accounting to flush before reading costs. The request cases below isolate this run. The five-minute initial request uses stream: false; all other requests stream

Before (b946d12ffdbd3a8379a78ed835bada22b9096207)

Baseline-identical first turn, nonstreaming

  1. Send the payload to POST http://localhost:47998/v1/messages; request msg_bdrk_bldf47nnemk3yrryrjnwbkxiupthzmhuojwnaa7pitfdspy57mkq completed
  2. Read GET http://localhost:47998/spend/logs?request_id=msg_bdrk_bldf47nnemk3yrryrjnwbkxiupthzmhuojwnaa7pitfdspy57mkq: actual $0.10216125, savings $+0.00000000

Baseline-identical first turn, streaming

  1. Send the payload to POST http://localhost:47998/v1/messages; request msg_bdrk_zxm5ntidn6h3utg567yoxeoy6sejdnpphrvwglwj6xopuw3efc7q completed
  2. Read GET http://localhost:47998/spend/logs?request_id=msg_bdrk_zxm5ntidn6h3utg567yoxeoy6sejdnpphrvwglwj6xopuw3efc7q: actual $0.16294500, savings $+0.00000000

Matching one-hour prefix

  1. Send the payload to POST http://localhost:47998/v1/messages; request msg_011CfDMZbczbz3fBG9rpWgeT completed
  2. Read GET http://localhost:47998/spend/logs?request_id=msg_011CfDMZbczbz3fBG9rpWgeT: actual $0.06521800, savings $-0.05664300

Changed one-hour prefix

  1. Send the payload to POST http://localhost:47998/v1/messages; request msg_011CfDMadF17pGRnvJfDttby completed
  2. Read GET http://localhost:47998/spend/logs?request_id=msg_011CfDMadF17pGRnvJfDttby: actual $0.06533400, savings $-0.05672550

Expired five-minute prefix

  1. Send the payload to POST http://localhost:47998/v1/messages; request msg_011CfDMwktxzMvt2suVbYRsH completed
  2. Read GET http://localhost:47998/spend/logs?request_id=msg_011CfDMwktxzMvt2suVbYRsH: actual $0.04085250, savings $-0.03224500

After (ed40241d26f6a23048c51d39bbb862a5e3a92de0)

Baseline-identical first turn, nonstreaming

  1. Send the payload to POST http://localhost:47999/v1/messages; request msg_bdrk_zxdz7r2x5dh2sze3ctjz2e7jem2oq2bbifyfjsggalkjslh3yc3a completed
  2. Read GET http://localhost:47999/spend/logs?request_id=msg_bdrk_zxdz7r2x5dh2sze3ctjz2e7jem2oq2bbifyfjsggalkjslh3yc3a: actual $0.10222375, savings $+0.00000000, identical_baseline_path, baseline $0.10222375

Baseline-identical first turn, streaming

  1. Send the payload to POST http://localhost:47999/v1/messages; request msg_bdrk_zj3mwsxxqic6rbi3es2ofnrurxaa2skjcxwolbayyp6gajguexlq completed
  2. Read GET http://localhost:47999/spend/logs?request_id=msg_bdrk_zj3mwsxxqic6rbi3es2ofnrurxaa2skjcxwolbayyp6gajguexlq: actual $0.16304500, savings $+0.00000000, identical_baseline_path, baseline $0.16304500

Matching one-hour prefix

  1. Send the payload to POST http://localhost:47999/v1/messages; request msg_011CfDNfwDzajr5TpXPhfYAN completed
  2. Read GET http://localhost:47999/spend/logs?request_id=msg_011CfDNfwDzajr5TpXPhfYAN: actual $0.06527800, savings unknown, token_count_timeout; comparison costs are null

Changed one-hour prefix

  1. Send the payload to POST http://localhost:47999/v1/messages; request msg_011CfDNgx1A614LdpAJq2GSs completed
  2. Read GET http://localhost:47999/spend/logs?request_id=msg_011CfDNgx1A614LdpAJq2GSs: actual $0.06539400, savings unknown, history_unavailable; comparison costs are null

Expired five-minute prefix

  1. Send the payload to POST http://localhost:47999/v1/messages; request msg_011CfDNhHJGK1iuq64KZ5CLj completed
  2. Read GET http://localhost:47999/spend/logs?request_id=msg_011CfDNhHJGK1iuq64KZ5CLj: actual $0.04087900, savings $+0.06131975, cache_prefix_cold, baseline $0.10219875

Python additions: 1,544 test lines / 2,442 runtime lines = 0.63:1

Type

Bug Fix

Caveats

Medium

  • Estimates hold recorded prompts, output usage and timing fixed
  • Unsupported semantics remain unknown after routing diverges
  • Provider eviction and unrecorded traffic remain unobservable
  • Spend logging must stay enabled for durable publication
  • Gateway count timeouts limited this run's live coverage
  • Current-tip interactive client and dashboard screenshots remain unverified

Low

  • One-hour natural expiry uses controlled-clock tests
  • Hosted checks and current-tip bot reviews are pending
  • Prior Veria resource-consumption findings await reassessment

Final Attestation

  • All current-tip checks and interactive-client verification complete; do not merge

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review baseline cache prefix matching, expiry, retry uncertainty, and savings coverage on this draft's current commit

@codspeed

codspeed Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_autorouter_baseline_cache (ed40241) with main (b946d12)

Open in CodSpeed

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

Comment thread litellm/litellm_core_utils/litellm_logging.py Outdated
Comment thread litellm/llms/anthropic/prompt_cache_prediction.py Dismissed
Comment thread litellm/proxy/spend_tracking/autorouter_baseline_cache.py Fixed
@tin-berri
tin-berri force-pushed the litellm_autorouter_baseline_cache branch from 16f151f to bec8a42 Compare September 15, 2026 03:45
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review bec8a42, including baseline prefix matching, expiry, estimate coverage, and reservation cleanup after estimator failures

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review bec8a42 for baseline cache prefix matching, expiry, estimate coverage, and reservation cleanup after estimator failures

@greptile-apps

greptile-apps Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The reviewed code appears safe to merge, with no new findings and no outstanding previous findings.

Findings

  1. P2 Critical-path database queries ▶

Summary

This PR replaces conversation-shape-based auto-router savings estimates with durable cache-prefix observations and replayable accounting.

  • Records cache-prefix history and baseline observations through the deferred spend pipeline.
  • Atomically reconciles request, session, and daily savings estimates while preserving billed spend.
  • Adds explicit estimate coverage, actual cost, baseline cost, and unknown-reason fields across APIs, CLI, and dashboard views.
  • Extends Anthropic cache-plan parsing, token counting, streaming usage accumulation, and model capability metadata.
  • Adds aligned Prisma schemas, forward migrations, and regression coverage for the new durable state.

Reviews (17) · Last reviewed commit: "fix(proxy): estimate auto-router baselin..."

Comment thread litellm/proxy/spend_tracking/autorouter_baseline_cache.py Outdated
Comment thread litellm/litellm_core_utils/litellm_logging.py
Comment thread litellm/proxy/hooks/autorouter_baseline_cache.py

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

Comment thread litellm/llms/anthropic/prompt_cache_prediction.py
@tin-berri
tin-berri force-pushed the litellm_autorouter_baseline_cache branch from bec8a42 to 146d1ab Compare September 15, 2026 04:16
@tin-berri

Copy link
Copy Markdown
Contributor Author

The quoted documentation rule explicitly exempts README.md; this change updates the existing router README with estimator behavior and coordination Redis requirements

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review 146d1ab, including atomic reservation recovery, active versus terminal retries, original-error preservation, and documented unavailable prefix counts

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run
Please review 146d1ab after the reservation lifecycle redesign, including storage recovery, retry ownership, partial streams, and failure telemetry

Comment thread litellm/proxy/hooks/autorouter_baseline_cache.py Fixed
Comment thread litellm/proxy/hooks/autorouter_baseline_cache.py Fixed

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

Comment thread litellm/litellm_core_utils/litellm_logging.py
@tin-berri
tin-berri force-pushed the litellm_autorouter_baseline_cache branch from 146d1ab to 0e4f2c7 Compare September 15, 2026 04:45
@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run
Please review 0e4f2c7, including terminal failure cleanup after retry exhaustion, retained attempt uncertainty, and original error telemetry

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review 0e4f2c7, which retires baseline reservations at the proxy terminal failure boundary while preserving per-attempt retry uncertainty

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

Comment thread litellm/proxy/spend_tracking/autorouter_baseline_cache.py Outdated
@tin-berri
tin-berri force-pushed the litellm_autorouter_baseline_cache branch from 0e4f2c7 to 1250ee5 Compare September 15, 2026 05:04
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review 1250ee5, including explicit invalidation cutoffs, late success ordering, and prompt recovery after terminal failures

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run
Please review 1250ee5, including explicit invalidation cutoffs, late success ordering, and prompt recovery after terminal failures

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

Comment thread litellm/proxy/spend_tracking/autorouter_baseline_cache.py Outdated
Comment thread litellm/proxy/spend_tracking/autorouter_baseline_cache.py Outdated
@tin-berri
tin-berri marked this pull request as ready for review September 15, 2026 07:06
@tin-berri
tin-berri requested a review from a team September 15, 2026 07:06
Comment thread litellm/proxy/spend_tracking/autorouter_baseline_cache.py Outdated
@tin-berri
tin-berri force-pushed the litellm_autorouter_baseline_cache branch from be0474b to 64610c1 Compare September 16, 2026 04:02
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review 64610c1: duplicate tag attribution is fixed through the shared identity resolver, and the handwritten catalog schema is updated.

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Please review 64610c1: duplicate tag attribution now uses one shared resolver, with nonzero PostgreSQL regression coverage and a killed multiplicity mutation.

@tin-berri

Copy link
Copy Markdown
Contributor Author

@veria-ai Please review 64610c1, including asynchronous baseline observations, bounded counting, retained-history retirement, and shared identity deduplication for daily attribution.

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

@tin-berri
tin-berri force-pushed the litellm_autorouter_baseline_cache branch from 64610c1 to e103981 Compare September 19, 2026 19:37
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review e103981 after rebasing onto current main, focusing on logging finalization and trusted spend metadata integration

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run Please review e103981 after the rebase, especially logging finalization, baseline accounting, and preservation of upstream metadata fields

@tin-berri

Copy link
Copy Markdown
Contributor Author

@veria-ai Please review e103981 after the rebase and reassess previous findings against the current baseline accounting and logging integration

@greptile-apps

greptile-apps Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff

These findings sit on lines the diff does not cover, so they could not be posted inline. Each one leaves this list once its file changes.

  • P2 Product documentation is misplaced litellm/router_strategy/complexity_router/README.md:17 ▶

    This section documents customer-facing LiteLLM behavior in-tree, violating the requirement to use litellm-docs before merging.

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

@tin-berri
tin-berri force-pushed the litellm_autorouter_baseline_cache branch from e103981 to ed40241 Compare September 19, 2026 19:44
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai Please review ed40241. Removed the product documentation from the implementation README and moved it into the litellm-docs guide

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run Please review ed40241. This revision only removes product documentation from the implementation README; runtime code is unchanged

@tin-berri

Copy link
Copy Markdown
Contributor Author

@veria-ai Please review ed40241 and reassess earlier findings. This revision removes misplaced documentation without changing runtime code from e103981

@cursor cursor Bot left a comment

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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ed40241. Configure here.

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai review

@tin-berri
tin-berri enabled auto-merge September 19, 2026 22:53
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.

4 participants