Repository navigation
feat(typesafe): backport the jev change set to stable/1.102.x for v1.102.1 - #42595
Conversation
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
|
bugbot run |
|
|
|
bugbot run |
| encrypted_params: Final = { | ||
| k: encrypt_value_helper(v) for k, v in updated_patch.litellm_params.model_dump(exclude_none=True).items() | ||
| k: ( | ||
| _effective_complexity_router_config(updated_patch.litellm_params, db_model.litellm_params) | ||
| if k == "complexity_router_config" | ||
| else encrypt_value_helper(v) | ||
| ) | ||
| for k, v in updated_patch.litellm_params.model_dump(exclude_none=True).items() |
There was a problem hiding this comment.
When a caller supplies jev_classifier_config.api_key, this branch persists the complexity-router configuration without encrypting the nested key. The create path also encrypts only the outer mapping, leaving nested strings unchanged. Creating or updating a JEV router with an inline key therefore stores the provider credential in plaintext instead of protecting it like other model credentials.
How this was verified: The accepted nested API-key field flows unchanged through the model create and update serialization paths into the database JSON.
Knowledge Base Used: Management APIs and resources
There was a problem hiding this comment.
Same lines as main since #41886: the backport carries them verbatim, and a stable line never redesigns persistence. Main fix tracked as LIT-8389
There was a problem hiding this comment.
Understood. This persistence behavior is intentionally carried over from #41886, and redesigning nested JEV credential encryption is out of scope for this stable-line backport. I’ll treat the issue as deferred to the main-branch fix tracked by LIT-8389 rather than a blocker for this PR.
| return validated | ||
| except ValidationError as exc: | ||
| location: Final = ".".join(str(part) for part in exc.errors()[0]["loc"]) | ||
| raise HTTPException(status_code=400, detail=f"Invalid member auto-router configuration at {location}.") from exc |
There was a problem hiding this comment.
Member saved JEV probe rejects credentials
Medium Severity
Member test_routing with saved_model_id revalidates the stored JEV config through _MemberJevClassifierConfig, which forbids api_key and api_base. A saved router that has TypeSafe credentials therefore fails member validation instead of using the server-side config.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 4bfac88. Configure here.
…pes for stable/1.102.x The committed schema.d.ts and lazy OpenAPI snapshot were taken verbatim from main by the #41615 and #41757 picks, so they described classifier types, routing causes, and a guardrail that this line does not ship and lacked the /auto_router/manage permission. Regenerated under Python 3.12, the interpreter the check-ui-api-types workflow uses.
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
1 issue from previous review remains unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d1cd2dc. Configure here.
…02.1) (#736) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.102.0` → `v1.102.1` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.102.1`](https://github.com/BerriAI/litellm/releases/tag/v1.102.1) [Compare Source](BerriAI/litellm@v1.102.0...v1.102.1) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.102.1 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.102.1/cosign.pub \ ghcr.io/berriai/litellm:v1.102.1 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - fix(anthropic): backport [#​42152](BerriAI/litellm#42152) and [#​42288](BerriAI/litellm#42288) to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42538](BerriAI/litellm#42538) - feat(typesafe): backport the jev change set to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42595](BerriAI/litellm#42595) - chore(release): backport [#​42388](BerriAI/litellm#42388) and [#​41462](BerriAI/litellm#41462) to stable/1.102.x by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42618](BerriAI/litellm#42618) **Full Changelog**: <BerriAI/litellm@v1.102.0...v1.102.1> </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/London) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDguMiIsInVwZGF0ZWRJblZlciI6IjQ0LjEwOC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.hayden.moe/hayden/phoebe/pulls/736
…02.1) (#2200) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.102.0` → `v1.102.1` | --- >⚠️ **Warning** > > Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/6) for more information. --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.102.1`](https://github.com/BerriAI/litellm/releases/tag/v1.102.1) [Compare Source](BerriAI/litellm@v1.102.0...v1.102.1) #### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.102.1 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.102.1/cosign.pub \ ghcr.io/berriai/litellm:v1.102.1 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** #### What's Changed - fix(anthropic): backport [#​42152](BerriAI/litellm#42152) and [#​42288](BerriAI/litellm#42288) to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42538](BerriAI/litellm#42538) - feat(typesafe): backport the jev change set to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42595](BerriAI/litellm#42595) - chore(release): backport [#​42388](BerriAI/litellm#42388) and [#​41462](BerriAI/litellm#41462) to stable/1.102.x by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42618](BerriAI/litellm#42618) **Full Changelog**: <BerriAI/litellm@v1.102.0...v1.102.1> </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Rome) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDQuMiIsInVwZGF0ZWRJblZlciI6IjQ0LjEwNC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.aresu.eu/GiorgioAresu/home-ops/pulls/2200
…02.1) (#267) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.102.0` → `v1.102.1` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.102.1`](https://github.com/BerriAI/litellm/releases/tag/v1.102.1) [Compare Source](BerriAI/litellm@v1.102.0...v1.102.1) #### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.102.1 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.102.1/cosign.pub \ ghcr.io/berriai/litellm:v1.102.1 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** #### What's Changed - fix(anthropic): backport [#​42152](BerriAI/litellm#42152) and [#​42288](BerriAI/litellm#42288) to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42538](BerriAI/litellm#42538) - feat(typesafe): backport the jev change set to stable/1.102.x for v1.102.1 by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42595](BerriAI/litellm#42595) - chore(release): backport [#​42388](BerriAI/litellm#42388) and [#​41462](BerriAI/litellm#41462) to stable/1.102.x by [@​devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#​42618](BerriAI/litellm#42618) **Full Changelog**: <BerriAI/litellm@v1.102.0...v1.102.1> </details> --- ### Configuration 📅 **Schedule**: (in timezone America/New_York) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDUuMiIsInVwZGF0ZWRJblZlciI6IjQ0LjEwNS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.greyrock.io/todd/home-ops/pulls/267


TLDR
Problem this solves:
How it solves it:
stable/1.102.xfor v1.102.1109ca70f66the picks build onstable/1.102.xalready carries the v1.102.1 bump (fix(anthropic): backport #42152 and #42288 to stable/1.102.x for v1.102.1 #42538)Included PRs, one commit per PR, cherry-picked with
-m 1from the main merge commit (plain pick for the #42301 squash), original author kept:deb9d8aedd, here3b483c80f6)cf42b607c3, hereb5ac61374e)34718f0da6, here79b72b8594)2edda5aec3, here4b0ffcdf41)1e161f516c, here92a2a2f924)a83773cfa5, heree20cfbeaeb)1106b16745, hered0687347a7)Two more commits on the branch:
1a3cec8f5e, ahead of feat(router): add TypeSafe Jev as a complexity router classifier #41615: the pieces of main109ca70f66(allow opted-in team members to manage their routers) that feat(router): add TypeSafe Jev as a complexity router classifier #41615 and fix(proxy): enforce virtual key budgets for JEV test routing #41879 build on: theDatabaseClientprotocol andAUTO_ROUTER_MANAGEpermission, theprisma_clientkwarg oncan_key_call_model/can_team_access_model, the lazyteam_membershipparams on_check_team_member_model_access(keeping this line'sProxyException, sinceModelAccessDeniedProxyExceptiondoes not exist here), the member dry-run plumbing inauto_router_endpoints.py, and the matching test helpers4bfac88281fix(ui): adapt the jev dashboard pieces to stable/1.102.x. The feat(auto-router): add JEV classifier alongside LLM classifier #41886 dashboard files imported./forecast_classifier_configand./AutoRouterClassifierTabsfrom feat(ui): configure capability and Fuse v2 classifiers #41315, which is not on this line, soclassifier_type_transition.tsis wired intoClassificationMethodConfigdirectly,classifier_llm_configis only serialized for LLM-backed classifiers (as on main),"hybrid"joins the classifier type union,heuristic_v2_success_threshold(not in this line's preset types) is dropped, and the jev connection-test params are hoisted into a named const so the dashboard lint budget stays at 551d1cd2dccafchore(backport): regenerate the openapi snapshot and dashboard api types for stable/1.102.x. The feat(router): add TypeSafe Jev as a complexity router classifier #41615 and feat(guardrails): add TypeSafe Jev relevance-based compaction guardrail #41757 picks brought main's committed_lazy_openapi_snapshot.jsonandschema.d.tsverbatim, so they described classifier types, routing causes,classifier_*fields and a guardrail this line does not ship and lacked/auto_router/manage; both were regenerated under Python 3.12 (the interpretercheck-ui-api-typesuses). Generated artifacts only, no runtime changeConflicts were resolved to what main has after each pick: #41615 in
litellm/types/utils.py,complexity_router.pyandconfig.py(this line lacks the capability and llm_v2 features, so the Jev hunks were applied on top of the line's code),auto_router_permissions.pyand its test taken from main since they do not exist here. #41879 inauto_router_endpoints.pyand its test around the member preview plumbing. #41886 in three python test files (formatting-only hunks kept as on the line, Jev additions applied) and about twelve dashboard files;StoredComplexityRouterConfigstill lives inedit_auto_router_modal.tsxon this line, so the jev and per-turn fields went there. #42301 ingateway/routes/allowlist.py(added/openrouter/),success_handler.py(the typesafe branch also matches the openrouter decisions route) and_lazy_openapi_snapshot.json(only the added openrouter path block).schema.d.tsand the openapi snapshot only received the hunks each pick adds, nothing regeneratedTests: every
tests/test_litellmfile the picks touch was run in the worktree, 1994 passed. The 14requires_semantic_routertests intest_complexity_router.pyfail on the line's tip before these picks too (semantic_routeris not installed) and were left alone. The UI workflows do not run forstable/**bases, so the dashboard checks were run locally at4bfac88281underui/litellm-dashboard: vitest on the touched test files (605 + 84 passed), eslint on every changed file (0 errors,local/no-large-inline-object-argbudget unchanged at 551),tsc --noEmit,npm run test:types,npm run build(the Dockerfile ui-builder stage), andmake checkat the repo root (PASS)User Flow
Before: an admin on stable 1.102.x cannot route through TypeSafe Jev at all, so the only way to get it is running the 1.103 rc in prod
stable/1.102.xwithTYPESAFE_API_KEYset and an auto routerauto-jevwhosecomplexity_router_confighas"classifier_type": "jev""model": "auto-jev"and gets 400Invalid model name passed in model=auto-jev(the router dropped that deployment at boot)"classifier_type": "jev"and gets 422Input should be 'heuristic', 'heuristic_v2', 'llm', 'custom', 'heuristic_first' or 'hybrid'jev-latestpayload and gets 404{"detail":"Not Found"}; POST https://litellm-domain/openrouter/alpha/decisions is 404 tooAfter: the same admin gets the JEV classifier, the TypeSafe passthroughs and the compaction guardrail on stable 1.102.x (v1.102.1)
"model": "auto-jev"and gets 200; thex-litellm-complexity-router-tierheader saysSIMPLEfor the short prompt andCOMPLEXfor the long one, withx-litellm-complexity-router-cause: jev_classifier; POST https://litellm-domain/v1/messages and POST https://litellm-domain/v1/responses with"model": "auto-jev"route the same way"classifier_type": "jev"and gets 200 withrouted_model,tierand thejev-classifier:SIMPLE/jev-confidence=...signals; with a key over budget the call is 400budget_exceededinstead of a free dry run"model": "db-auto-jev"route the same wayLiteLLM Virtual Key expected; POST https://litellm-domain/openrouter/alpha/decisions with"model": "typesafe/jev-1.13"is 200 with"model":"typesafe/jev-1.13-20260917"JEV ClassifierwithJEV ModelandJEV Timeout (ms)fields"guardrails": ["typesafe-compaction"]over a long tool conversation is billed at 962 prompt tokens instead of 1119 for the same chat without itRelevant issues
Backport of #41607, #41615, #41723, #41757, #41879, #41886 and #42301 to
stable/1.102.xAffected release
Linear ticket
Resolves LIT-8369
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@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
Two proxies from the same config, each with its own empty Postgres, real provider calls (Anthropic, TypeSafe, OpenRouter) with real spend. Before is the
stable/1.102.xmerge base95293834e8on port 16390, After is the PR tip4bfac88281on port 16391 (its process runs the Python tree ofd0687347a7, which is byte-identical to the tip outsideui/litellm-dashboard; the dashboard screenshots come from the bundle built at4bfac88281). Both started withconfig.yaml(LITELLM_MASTER_KEY,DATABASE_URL,ANTHROPIC_API_KEY,TYPESAFE_API_KEYin the environment):Keys used below, generated the same way on both sides:
$MASTER_KEYis the config's master key;$KEYcomes fromPOST /key/generate {"key_alias":"ab-plain","max_budget":10}(an internal-user key);$ADMIN_KEYand$ADMIN_KEY_EXHAUSTEDare keys of aproxy_adminuser created withPOST /user/new {"user_id":"ab-admin","user_role":"proxy_admin","auto_create_key":false}, generated withmax_budget10 andmax_budget0 ("metadata":{"throttle_on_budget_exceeded":true}) respectively.$BASEishttp://localhost:16390for Before andhttp://localhost:16391for After. The compaction case sends this 9-message tool conversation:The
/v1/messagesand/v1/responsescases ran last, against the same two trees booted from the same config minusdatabase_url(ports 17490 and 17491, master key only), after the databases above were torn downtool conversation payload
{ "model": "claude-haiku-4-5", "max_tokens": 60, "guardrails": [ "typesafe-compaction" ], "messages": [ { "role": "system", "content": "You are a deployment assistant." }, { "role": "user", "content": "What is the weather in Paris?" }, { "role": "assistant", "content": null, "tool_calls": [ { "id": "c1", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" } } ] }, { "role": "tool", "tool_call_id": "c1", "content": "Paris: 18C, light rain, humidity 72 percent, wind 12 km/h from the west, UV index 2, sunrise 07:14, sunset 19:52, pollen low, air quality index 31, visibility 9 km, pressure 1013 hPa, dew point 13C, cloud cover 80 percent, chance of rain 60 percent through the evening." }, { "role": "user", "content": "And the ACME stock price?" }, { "role": "assistant", "content": null, "tool_calls": [ { "id": "c2", "type": "function", "function": { "name": "get_quote", "arguments": "{\"ticker\":\"ACME\"}" } } ] }, { "role": "tool", "tool_call_id": "c2", "content": "ACME: 142.17 USD, change +1.3 percent, open 140.02, high 143.10, low 139.80, volume 2.1M, market cap 48B, PE 31.2, dividend yield 0.8 percent, 52 week high 155.00, 52 week low 98.40, next earnings 2026-10-21, analyst consensus hold, average target 150.00." }, { "role": "user", "content": "Show me the deployment runbook for the payments service." }, { "role": "assistant", "content": null, "tool_calls": [ { "id": "c3", "type": "function", "function": { "name": "get_runbook", "arguments": "{\"service\":\"payments\"}" } } ] }, { "role": "tool", "tool_call_id": "c3", "content": "Payments runbook: 1) freeze deploys in #payments-ops, 2) run db migration payments-2026-09 with --dry-run first, 3) scale canary to 5 percent, 4) watch p99 latency and error rate for 15 minutes, 5) if error rate exceeds 0.5 percent roll back with deploy rollback payments, 6) otherwise promote to 100 percent and unfreeze, 7) post the summary in #payments-ops with the deploy id." }, { "role": "user", "content": "What is step 5 of the runbook?" } ] }Before (9529383, port 16390)
Chat through the JEV auto router
{"error":{"message":"/chat/completions: Invalid model name passed in model=auto-jev. Call/v1/modelsto view available models for your key.","type":"invalid_request_error","param":null,"code":"400","provider_specific_f...{"error":{"message":"/chat/completions: Invalid model name passed in model=auto-jev. Call/v1/modelsto view available models for your key.","type":"invalid_request_error","param":null,"code":"400","provider_specific_f...Messages and Responses through the JEV auto router
{"type":"error","error":{"type":"invalid_request_error","message":"anthropic_messages: Invalid model name passed in model=auto-jev. Call/v1/modelsto view available models for your key.","provider_...max_tokens200{"type":"error","error":{"type":"invalid_request_error","message":"anthropic_messages: Invalid model name passed in model=auto-jev. Call/v1/modelsto view available models for your key.","provider_...{"error":{"message":"/responses: Invalid model name passed in model=auto-jev. Call/v1/modelsto view available models for your key.","type":"invalid_request_error","param":null,"code":"400","provid...max_output_tokens200{"error":{"message":"/responses: Invalid model name passed in model=auto-jev. Call/v1/modelsto view available models for your key.","type":"invalid_request_error","param":null,"code":"400","provid...Dry-run routing with the JEV classifier
{"detail":[{"type":"literal_error","loc":["body","complexity_router_config","classifier_type"],"msg":"Input should be 'heuristic', 'heuristic_v2', 'llm', 'custom', 'heuristic_first' or 'hybrid'","input":"jev","ctx":{"expected":"'heuristic', 'heuristic_v2', 'll...$ADMIN_KEY_EXHAUSTED(max_budget 0){"detail":[{"type":"literal_error","loc":["body","complexity_router_config","classifier_type"],"msg":"Input should be 'heuristic', 'heuristic_v2', 'llm', 'custom', 'heuristic_first' or 'hybrid'","input":"jev","ctx":{"expected":"'heuristic', 'heuristic_v2', 'll...$KEY(a plain internal-user key){"detail":[{"type":"literal_error","loc":["body","complexity_router_config","classifier_type"],"msg":"Input should be 'heuristic', 'heuristic_v2', 'llm', 'custom', 'heuristic_first' or 'hybrid'","input":"jev","ctx":{"expected":"'heuristic',...TypeSafe passthrough
HTTP 404,
{"detail":"Not Found"}PUTon the same route with{"state":"x"}HTTP 404,
{"detail":"Not Found"}Same
POSTwithAuthorization: Bearer not-a-keyHTTP 404,
{"detail":"Not Found"}Same
POSTwith odd payloads:"state": 7{"detail":"Not Found"}"state": ["a","b"]{"detail":"Not Found"}"state": ""{"detail":"Not Found"}state{"detail":"Not Found"}{"detail":"Not Found"}OpenRouter decisions passthrough
{"detail":"Not Found"}TypeSafe compaction guardrail
usage.prompt_tokens1119guardrailsusage.prompt_tokens1119JEV auto router created through the API
{"error":{"message":"complexity_router_config is invalid at classifier_type: Input should be 'heuristic', 'heuristic_v2', 'llm', 'custom', 'heuristic_first' or 'hybrid'. The router would drop this deployment at load time, so the write is rejected instead.","type":"validation_error","param":"litellm_...POST $BASE/v1/chat/completionswith"model": "db-auto-jev"and the capital-of-France prompt{"error":{"message":"/chat/completions: Invalid model name passed in model=db-auto-jev. Call/v1/modelsto view available models for your key.","type":"invalid_request_error","param":null,"code":"400","provider_specifi...TypeSafe guardrail created through the API
{"detail":"400: Guardrail configuration error: Unsupported guardrail: typesafe"}Admin UI classifier picker
Open
$BASE/ui/models-and-endpoints, click theAuto-Routerstab, clickAdd Auto Router, expandDetailed Configuration, thenAdvanced: Classification MethodThe picker offers Heuristic, Heuristic v2, LLM Classifier, Heuristic first and Hybrid; there is no JEV option
Existing surfaces (regression sweep)
Each row below is one curl of the form used above (same
$BASE, the key named in the row, the same simple and hard prompts), run in the listed order on this sideObserved, one row per call:
auto-llm, simple promptauto-llm, tier SIMPLEauto-llm, hard promptauto-llm, tier COMPLEXauto-heuristicauto-heuristic, tier SIMPLEclaude-haiku-4-5claude-haiku-4-5claude-haiku-4-5claude-haiku-4-5auto-llmauto-llm, tier SIMPLE{"error":{"message":"Budget has been exceeded! Key=ab-exhausted (sk-...) Current cost:...{"error":{"message":"Budget has been exceeded! Key=ab-admin-exhausted (sk-...) Current...{"detail":{"error":"User does not have permission to dry-run an auto router. Your role=int...{"detail":{"error":"User does not have permission to dry-run an auto router. Your role=int...db-auto-llm(llm classifier)db-auto-llmdb-auto-llm, tier SIMPLEAfter (4bfac88, port 16391)
The tip is
d1cd2dccaf; it adds only the regenerated OpenAPI snapshot and the dashboard.d.tstypes on top of4bfac88281, neither of which is executed on any route or screen below, so this run stands for the tipChat through the JEV auto router
modelauto-jev, headerx-litellm-complexity-router-tier: SIMPLE,x-litellm-complexity-router-cause: jev_classifier, prompt_tokens 19 completion_tokens 4modelauto-jev, headerx-litellm-complexity-router-tier: COMPLEX,x-litellm-complexity-router-cause: jev_classifier, prompt_tokens 67 completion_tokens 6903Messages and Responses through the JEV auto router
modelauto-jev, headerx-litellm-complexity-router-tier: SIMPLE,x-litellm-complexity-router-cause: jev_classifier, input_tokens 19 output_tokens 4max_tokens200modelauto-jev, headerx-litellm-complexity-router-tier: COMPLEX,x-litellm-complexity-router-cause: jev_classifier, input_tokens 67 output_tokens 4906modelauto-jev, headerx-litellm-complexity-router-tier: SIMPLE,x-litellm-complexity-router-cause: jev_classifier, input_tokens 19 output_tokens 4max_output_tokens200modelauto-jev, headerx-litellm-complexity-router-tier: COMPLEX,x-litellm-complexity-router-cause: jev_classifier, input_tokens 67 output_tokens 6586Dry-run routing with the JEV classifier
{"routed_model":"claude-haiku-4-5","tier":"SIMPLE","cause":"jev_classifier","classifier_model":"typesafe/jev-1.13.0","signals":["jev-classifier:SIMPLE","jev-confidence=1.000000","tier-probability:REASONING=0.000000","tier-probability:MEDIUM=0.000000","tier-probability:SIMPLE=1.000000","tier-probability:COMPLEX=0.000000"]}$ADMIN_KEY_EXHAUSTED(max_budget 0){"error":{"message":"Budget has been exceeded! Key=ab-admin-exhausted (sk-...) Current cost: 0.0, Max budget: 0.0","type":"budget_exceeded","param":null,"code":"400"}}$KEY(a plain internal-user key){"detail":{"error":"User does not have permission to dry-run an auto router. Your role=internal_user. Call as a PROXY_ADMIN, or as a team admin by specifying a team_id."}}TypeSafe passthrough
HTTP 200,
{"model":"jev-1.13.0","answers":{"blue":{"type":"noul","noul":0.99}},"usage":{"input_tokens":282,"output_tokens":20}}PUTon the same route with{"state":"x"}HTTP 405,
{"detail":"Method Not Allowed"}Same
POSTwithAuthorization: Bearer not-a-keyHTTP 401,
{"error":{"message":"LiteLLM Virtual Key expected. Received=not-****-key, expected to start with 'sk-'.","type":"auth_error","param":"None","code":"401"}}Same
POSTwith odd payloads:"state": 7{"detail":[{"type":"string_type","loc":["body","state","str"],"msg":"Input should be a valid string","input":7..."state": ["a","b"]{"model":"jev-1.13.0","answers":{"q":{"type":"noul","noul":0.97}},"usage":{"input_tokens":278,"output_tokens":..."state": ""{"model":"jev-1.13.0","answers":{"q":{"type":"noul","noul":0.8}},"usage":{"input_tokens":270,"output_tokens":2...state{"model":"jev-1.13.0","answers":{"q":{"type":"noul","noul":0.96}},"usage":{"input_tokens":1832,"output_tokens"...{"model":"jev-1.13.0","answers":{"blue":{"type":"noul","noul":0.99}},"usage":{"input_tokens":282,"output_token...OpenRouter decisions passthrough
{"model":"typesafe/jev-1.13-20260917","answers":{"blue":{"type":"noul","noul":0.99}},"usage":{"input_tokens":282,"output_tokens":20,"cost":1.1844e-05},"id":"gen-dec-1790127449-uSzKbgQH085seSsxNoWK","provider":"TypeSafe"}TypeSafe compaction guardrail
usage.prompt_tokens962guardrailsusage.prompt_tokens1119JEV auto router created through the API
model_idc746f2c7-6a20-4d9b-8536-5d3baab25654POST $BASE/v1/chat/completionswith"model": "db-auto-jev"and the capital-of-France promptmodeldb-auto-jev, headerx-litellm-complexity-router-tier: SIMPLE,x-litellm-complexity-router-cause: jev_classifier, prompt_tokens 19 completion_tokens 4TypeSafe guardrail created through the API
guardrail_nametypesafe-db(body holds the guardrail id and params, not shown)Admin UI classifier picker
Open
$BASE/ui/models-and-endpoints, click theAuto-Routerstab, clickAdd Auto Router, expandDetailed Configuration, thenAdvanced: Classification MethodThe picker adds
JEV Classifier("uses TypeSafe System One Choice to decide the tier")Selecting it shows the
JEV Model(jev-latest) andJEV Timeout (ms)(3000) fields plus the classifier circuit breaker toggleExisting surfaces (regression sweep)
Each row below is one curl of the form used above (same
$BASE, the key named in the row, the same simple and hard prompts), run in the listed order on this sideObserved, one row per call:
auto-llm, simple promptauto-llm, tier SIMPLEauto-llm, hard promptauto-llm, tier COMPLEXauto-heuristicauto-heuristic, tier SIMPLEclaude-haiku-4-5claude-haiku-4-5claude-haiku-4-5claude-haiku-4-5auto-llmauto-llm, tier SIMPLE{"error":{"message":"Budget has been exceeded! Key=ab-exhausted (sk-...) Current cost:...{"error":{"message":"Budget has been exceeded! Key=ab-admin-exhausted (sk-...) Current...{"detail":{"error":"User does not have permission to dry-run an auto router. Your role=int...{"detail":{"error":"User does not have permission to dry-run an auto router. Your role=int...db-auto-llm(llm classifier)db-auto-llmdb-auto-llm, tier SIMPLELive PR risk
Verdict: no breaking or backward-incompatible change on an existing surface. Every call that existed before (25 in the sweep above, plus the delete-then-re-add of a DB auto router) returns the same status and the same routing tier on both sides
Breaking: none found. The base of every existing route, the config keys, the DB schema and the dashboard routes are unchanged; the picks only add the
jevclassifier literal, thetypesafeguardrail, the/typesafe/*and/openrouter/*pass-through routes and the dashboard picker entryBackward incompatible:
POST /auto_router/test_routingwith"classifier_type": "jev"moves from 422 (unknown literal) to the same permission and budget checks the other classifiers already get (403 for an internal-user key, 400budget_exceededfor an exhausted key, 200 otherwise). Nothing that returned 200 before returns anything else nowRegression risk:
POST /model/delete, thenPOST /model/newwith the same name) works on both sides: delete 200,/model/infoempty, re-add 200, chat 200. One head re-add during QA returned 500not live in this pod's router after the reload; the cause was orphan DB rows created while the rig was overloaded (rows saved to the DB but refused by the router's duplicate-name guard, which holds the name until the orphan is deleted).litellm/router.pyis byte-identical between the merge base and the tip and the guard exists on main, so it is not something this PR changesLiteLLM_SpendLogsat the tip:typesafe/jev-1.13.0andopenrouter/typesafe/jev-1.13-20260917rows carry token counts and non-zero spend, and the classifier call of every JEV-routed chat is its own row next to the routed model's rowDependency graph:
test_routingpermission and budget path,/model/newvalidation ofcomplexity_router_config, guardrail registry (typesafe), pass-through route registration and auth (/typesafe/*,/openrouter/alpha/decisions), pass-through spend logging, the dashboard add-auto-router modalNot verified: streaming through a JEV router, the edit modal, rolling upgrade
Type
🆕 New Feature
Caveats (if any)
Medium
stable/**bases, so the dashboard checks ran locally at the tip (vitest, eslint, tsc, test:types,npm run build,make check)Low
95293834e8),scripts/type_discipline_gate.pycounts LIT001 at 22188 vs the line's limit 22174 (+15, in the pickedauth_checks.pyandguardrail_hooks/typesafe/) andscripts/test_quality_gate.pycounts TQ008 at 11151 vs 10993 (+73, in pick-modified test files). The code is main's verbatim, the workflows enforcing these budgets do not run forstable/**bases, and budget JSON files are never edited on a branch, so the counts are left as they are rather than refactoring main's code on a stable linetest_routingwith an internal-user key is 403 before and after (needs PROXY_ADMIN or a team admin), so the JEV dry run has the same audience as the other classifiersrequires_semantic_routertests intest_complexity_router.pyfail on this line with or without the picks (semantic_routeris not installed here)max_tokens(the hard prompts come back with thousands of completion tokens atmax_tokens40); this is how the router already behaves on the merge base with the llm classifier, not something the picks change4bfac88281instead of pulling it inFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/b6d5b6c20aec42babbd9213ec27ef3bd
Open in Devin Desktop: https://app.devin.ai/desktop/session/b6d5b6c20aec42babbd9213ec27ef3bd?variant=devin
Requested by: @mateo-berri
Note
Medium Risk
Broad additive changes across routing, auth, guardrails, and external TypeSafe/OpenRouter calls; existing routes stay compatible but misconfiguration or upstream outages can block or alter requests on new JEV paths.
Overview
This backport brings TypeSafe Jev onto the stable proxy line: new
/typesafe/*and/openrouter/*pass-through routes (with token/cost logging), atypesafeguardrail that compacts stale tool results via Jev/v1/systemone, andclassifier_type: "jev"on complexity auto-routers (tier choice, circuit breaker, classifier probabilities in routing logs).Auto-router dry-runs and member-managed routers gain stricter gates:
/auto_router/manageteam permission, dependency/model checks for non-admins, optionalsaved_model_idon test routing, and virtual-key budget enforcement for JEV test routing. Model writes merge stored Jev credentials when patching configs; router health sweeps skipevaluationdependencies so Jev classifiers do not mark deployments unhealthy.Pricing/schema updates add
evaluationmode and Jev model entries; allowlists and lazy route loading register the new pass-through prefixes.Reviewed by Cursor Bugbot for commit d1cd2dc. Bugbot is set up for automated code reviews on this repo. Configure here.
Link to Devin session: https://app.devin.ai/sessions/dcedce8035d64a199713bae4b2b90b7f
Open in Devin Desktop: https://app.devin.ai/desktop/session/dcedce8035d64a199713bae4b2b90b7f?variant=devin