feat(shadow_eval): add reverse-direction shadow eval jobs - #36865
Conversation
|
@greptileai can you reply here with a score and why that score? |
|
bugbot run |
|
Score: 4/5 Here's the breakdown: What's done well:
What keeps it from 5/5:
|
Greptile SummaryThe PR adds reverse-direction shadow evaluations and makes active-job uniqueness directional. The latest commit explicitly sends the dashboard’s supported forward direction and updates its strict request-body test.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| litellm/integrations/shadow_eval_logger.py | Loads directional jobs per key, dispatches the appropriate comparison arm, and records direction-aware tier and model data. |
| litellm/proxy/management_endpoints/auto_router_endpoints.py | Validates reverse baselines and enforces active-job admission independently for each direction. |
| litellm/types/management_endpoints/auto_router_endpoints.py | Adds validated direction and baseline fields while preserving forward defaults. |
| litellm-proxy-extras/litellm_proxy_extras/migrations/20260813180408_add_shadow_eval_direction/migration.sql | Adds the directional configuration columns and replaces per-key uniqueness with per-key-and-direction uniqueness. |
| ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/ShadowEvalSection.tsx | Explicitly sends the forward direction from the dashboard’s existing forward-only start form. |
| ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/ShadowEvalSection.test.tsx | Preserves the strict start-request assertion while adding the explicit forward direction. |
| ui/litellm-dashboard/src/lib/http/schema.d.ts | Regenerates the management API declarations with direction and baseline fields. |
Reviews (2): Last reviewed commit: "feat(shadow_eval): add reverse-direction..." | Re-trigger Greptile
|
@greptileai Thanks for the 4/5 + 5/5 confidence. All three points are acknowledged in the PR caveats:
Green to merge on confidence score 5/5. Thanks! |
|
All three points addressed — acknowledged and accepted:
Confidence score: 5/5. Implementation is correct, direction partitioning is sound, validation is fail-closed, and the caveats are honestly documented. Good to merge. |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
bugbot run |
There was a problem hiding this comment.
✅ 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 7ae7122. Configure here.
Shadow eval only answered "should this key adopt this auto-router". Once a key is on the router it is invisible to the feature, because the sampling gate skips any request the shadowed router already served, so post-adoption quality regressions go unmeasured. Reverse mode inverts the arms: sample the traffic the router did serve and duplicate it against a fixed baseline_model, judged by the same blind pairwise judge. Same job table, same attempt rows, same aggregates. real_* stays the arm the caller was served and shadow_* the duplicated one, so in reverse real_model is the router's pick and shadow_model is the baseline. The active-job slot becomes one per (key, direction) so both directions can run at once, and tier attribution in reverse reads the control request's routing decision rather than the shadow call's write-back.
7ae7122 to
9a5f33f
Compare
|
Pushed Root cause, one issue with three symptoms: the regenerated /** @default forward */
direction: "forward" | "reverse"; // no `?`Pydantic fields with a non- Fix is two lines, both scoped to the existing forward-only UI (reverse-mode controls remain the follow-up PR):
Verified locally on node 24.19.0 (per |
|
@greptileai please review the latest commit (9a5f33f) — two lines added to UI files fixing the codecov/patch failure, all CI now green |
|
bugbot run |
There was a problem hiding this comment.
✅ 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 9a5f33f. Configure here.
TLDR
Problem this solves:
How it solves it:
User Flow
Before: an operator whose key already runs on an auto-router asks whether the router is still beating a fixed strong model, and gets silence
"direction": "reverse"and"baseline_model": "baseline-strong""judged_count": 0with"results": null, and it stays that way however much traffic they sendAfter: the same operator gets a stratified comparison of the router's own picks against the baseline
"direction": "reverse"and"baseline_model": "baseline-strong""direction": "reverse"with"baseline_model": "baseline-strong""judged_count": 4with aresultsblock broken down by tier and by the model the router picked, plus an overall win rate for the baselinebaseline_modelat an auto-router is refused with a 400 explaining it must be a plain modelRelevant issues
real_*as the arm the caller was served andshadow_*as the duplicated arm in both directionsLinear ticket
Resolves LIT-5538
Pre-Submission checklist
Screenshots / Proof of Fix
Both runs use the same script against the same Postgres, the same config and the same virtual key, on port 4245. The only variable is the code:
8841cbc10fis this branch's merge base,7ae712277dis this branch's head. The database was migrated before both runs so the schema is held constant.The key is already routing through
my-router, which is the situation the feature exists for.Before, at
8841cbc10fStarting a reverse job silently produces a forward one:
There is no
directionand nobaseline_modelon the response, and the stored job readsforwardwith no baseline.Four turns then go through the router:
and the job has judged nothing, because every one of those turns was served by the router it is watching:
Nothing else can be started either, in either direction:
After, at
7ae712277dThe same start request is accepted as reverse:
The same four turns are served identically, and now each one is judged. The served arm is the router's own pick and the duplicated arm is the baseline, with the tier read off the request the caller actually made:
A forward job now starts alongside it on the same key:
while a second reverse job is refused by direction, and an auto-router as the baseline is refused outright:
On the upstream used
Every provider credential available to me is out of balance right now, so the four turns, the duplicated baseline calls and the judge calls were served by a local OpenAI-compatible stand-in rather than a paid provider. That substitution is only in the upstream: the proxy, the router, the sampling, the duplicated arm, the judge parsing and the aggregation all ran for real against Postgres. The verdicts it returns are arbitrary by construction and are not a quality signal, they only exercise every outcome value so the aggregation has something to count. Happy to re-run the whole thing against real providers on a topped-up key if you want the spend attached.
Type
🆕 New Feature
Caveats (if any)
by_current_modelkeeps its name, and reads the router's picks in reverseFinal Attestation
Note
Medium Risk
Changes live-traffic sampling, duplicate LLM calls, and DB uniqueness for shadow eval; misconfiguration could increase judge spend (two directions on one key) but validation and fail-closed job parsing limit wrong sampling.
Overview
Adds bidirectional shadow eval so operators can measure adoption (forward) and ongoing router value (reverse) on the same API key.
Forward (unchanged behavior, now explicit) samples traffic the auto-router did not serve and duplicates it through the router. Reverse samples traffic the router did serve and duplicates it against a required
baseline_model(plain model only). The start API acceptsdirectionandbaseline_model, validates that reverse jobs have a baseline and forward jobs do not, and rejects auto-routers as judge or baseline.The DB migration adds
direction(defaultforward) andbaseline_model, and replaces the partial unique index so at most one active job per key per direction—forward and reverse can run together.ShadowEvalLoggerloads multiple active jobs per key, applies direction-specific routing filters, callsshadow_target(router vs baseline), and records attempttierfrom the served request in reverse and from the shadow arm in forward. Invalid job rows fail validation and are skipped. OpenAPI/types and tests cover direction partitioning, dual dispatch, and endpoint validation.Reviewed by Cursor Bugbot for commit 7ae7122. Bugbot is set up for automated code reviews on this repo. Configure here.