fix(complexity_router): stop promising the classifier prior turns it will not receive - #35690
fix(complexity_router): stop promising the classifier prior turns it will not receive#35690tin-berri wants to merge 1 commit into
Conversation
Greptile SummaryThis PR makes prior-turn context opt-in for the complexity router’s LLM classifier while preserving the existing behavior for explicitly configured positive windows.
Confidence Score: 5/5The PR appears safe to merge; the new default is consistently applied across backend and dashboard paths, while explicit positive-window configurations retain their prior behavior. The classifier suppresses prior-turn payload and framing only when the validated window is zero, positive windows preserve the existing prompt, and dashboard creation and editing paths correctly retain numeric zero.
|
| Filename | Overview |
|---|---|
| litellm/router_strategy/complexity_router/complexity_router.py | Splits classifier prompt framing into ask-only and prior-turn variants while preserving the positive-window prompt contract. |
| litellm/router_strategy/complexity_router/config.py | Changes the validated classifier context-window default to zero and updates its operator-facing description. |
| tests/test_litellm/router_strategy/test_complexity_router.py | Adds regression coverage for unconfigured ask-only classification and keeps context-aware tests explicitly opted into a positive window. |
| ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx | Updates helper text to explain the zero default and preserves numeric zero through nullish handling. |
| ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx | Mirrors the backend context-window default of zero in the dashboard. |
| ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx | Updates dashboard expectations to verify that LLM classifier forms initialize the context window to zero. |
Reviews (1): Last reviewed commit: "fix(complexity_router): default the clas..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 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 045f753. Configure here.
…will not receive The classifier's system role closes on a line chosen by the window, so a deployment at classifier_context_window_size: 0 is told to classify only the current message. The trust boundary paragraph above it was not conditional, and it names prior turns as quoted material, so the same system role still promised sections the payload never carries. That is the defect #35504 fixed for the closing line, one paragraph earlier. The boundary now tracks the window the same way. The half that defends against a caller's own system prompt stays unconditional, because that block is quoted at every window setting and dropping it would let a key scoped to the router pin itself to the top tier through its own system prompt. At a window above 0 the system role is byte-identical to before.
045f753 to
3926ac2
Compare
Pull request was closed
TLDR
Problem this solves:
classifier_context_window_size: 0is told to classify only the current message. The trust boundary paragraph above that line was not conditional, and it names prior turns as quoted material, so the same system role still promised the model sections its payload never carriesHow it solves it:
DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZEis untouched at 3. Whether that default is right is a separate question with a spend implication, and it does not belong in a correctness fixRelevant issues
classifier_context_include_assistant_turnsLinear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Live proxy on :4000 with two auto-routers over the same tiers, one at
classifier_context_window_size: 0and one leaving it at the default of 3. Both classify through a real haiku-4-5 call against a real providerSame multi-turn conversation to each
The classifier's system role in each case, read back out of the
--detailed_debuglog. Note the log renders these inside Python reprs, so the apostrophe in "caller's" is backslash-escaped in some of them; unescape before matching or the counts come out wrongFour calls per arm. The count of system roles promising prior turns matches the count of payloads that actually quote them, which is the invariant this PR restores. The two sentences as sent
Before this change both arms sent the second sentence, so the
auto-window0arm promised turns it never quotes. The injection-defense clause is identical in both, which is the part that must not become conditionalPrompt size, since the diff moves prompt text between constants. Nothing grew, and the window-above-0 path did not move at all
Type
🐛 Bug Fix
Changes
_CLASSIFICATION_SYSTEM_RUBRICsplits into_CLASSIFICATION_TIERSplus two trust boundary variants, so_classification_system_promptcan select the one that matches the payload. No wording changed in the path that quotes turns. The ask-only variant is the same sentence with the prior-turn clause removed and "Those sections are" made singular; the injection-defense clause is identical in bothThe
_classify_with_llmdocstring said the system message carries the caller's own system prompt, and that keeping it there lets providers prompt-cache it. Neither has been true since #35185's review moved the caller's prompt into the user role so it could not carry instruction authority, and nothing in this file sets acache_controlbreakpoint, so no provider caches any of it. It now describes the split as the trust boundary it isTests: the existing framing test gains assertions for the boundary half, and the window-of-zero payload test now also asserts the system role. Three mutants killed, covering both directions of the branch plus deletion of the injection-defense clause
QA runbook
python litellm/proxy/proxy_cli.py --config <the config above> --port 4000 --detailed_debug 2>&1 | tee litellm.logforloop abovegrepfor those sentences will undercount, because the log escapes the apostrophe in "caller's" inside some reprsauto-defaultarm's payload still carries itsRecent conversationblock, so the opt-in path is unchangedFinal Attestation