Repository navigation
feat(router): add TypeSafe Jev as a complexity router classifier - #41615
Conversation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…tor argument Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
| async def evaluate(self, request: JevSystemOneRequest, timeout_s: float) -> JevSystemOneResponse: | ||
| response: Final = await self._http_client.post( # pyright: ignore[reportUnknownMemberType] # AsyncHTTPHandler has a dynamic post signature | ||
| f"{self._api_base}/v1/systemone", | ||
| json=request.model_dump(mode="json"), | ||
| headers=MappingProxyType( | ||
| { | ||
| "Authorization": f"Bearer {self._api_key}", | ||
| "Content-Type": "application/json", | ||
| } | ||
| ), # pyright: ignore[reportArgumentType] # HTTP headers are not mutated by AsyncHTTPHandler | ||
| timeout=timeout_s, | ||
| ) | ||
| response.raise_for_status() | ||
| return TypeAdapter(JevSystemOneResponse).validate_python(response.json()) |
There was a problem hiding this comment.
Provider Code Misplaced This TypeSafe transport logic violates the requirement to keep provider-specific code under
llms/; move it before merging
Rule Used: What: Avoid writing provider-specific code outside of the llms/ directory. Why: This practice ensures better maintainability and reduces complexity over time. Good: ```python # Handle provider-specific logic within llms/vertex_ai/transformation.py ... (source)
Knowledge Base Used: Provider adapters and capabilities
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Jev is a router classifier adapter, not an LLM provider implementation; its dedicated System One protocol belongs with complexity-router strategy code.
There was a problem hiding this comment.
You're right. Jev is being used as a complexity-router classifier, and jev_classifier.py contains the router-specific System One protocol adapter rather than an LLM provider integration. Keeping it under router_strategy/complexity_router/ is appropriate, so this finding should be withdrawn.
There was a problem hiding this comment.
Complexity-router classifiers already own classifier-specific transport, while llms adapters serve model completion APIs; moving Jev would misclassify router infrastructure as provider completion code.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2 similar comments
|
bugbot run |
|
bugbot run |
|
bugbot run |
|
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 853fd04. Configure here.
Partial backport of upstream BerriAI#41607 (price rows and the "evaluation" model mode only; the /typesafe pass-through is not carried), plus the test_models_by_provider skip from BerriAI#41723. The Jev classifier (BerriAI#41615) prices its calls from typesafe/jev-* registry rows, so they must exist even when LITELLM_LOCAL_MODEL_COST_MAP is set. Drop this commit when rebasing onto a release that contains BerriAI#41607. Co-Authored-By: Mateo Wang <277851410+mateo-berri@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Backport of upstream BerriAI#41615 (merge cf42b60) onto v1.101.0 + MongoDB vector store work. Adapted to the 1.101 base: - Jev tier criteria cover the four built-in tiers only (1.101 has no NON_REASONING tier); enable_non_reasoning_tier is not carried. - _with_classifier_forecast only attaches the Jev verdict (1.101 has no capability / llm_v2 forecasts). - auto_router_permissions.py does not exist on 1.101, so the team-member Jev restriction is not carried. JevClassifierConfig still refuses an api_base without its own api_key, so TYPESAFE_API_KEY is never sent to a caller-chosen host. - schema.d.ts is regenerated in a later commit. Drop this commit when rebasing onto a release that contains BerriAI#41615. (cherry picked from commit cf42b60) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…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.
TLDR
Problem this solves:
How it solves it:
classifier_type: jevwithjev_classifier_configon the complexity routerchoicequestion; the choice picks the tierTYPESAFE_API_KEYis only ever sent to TypeSafe's own hostUser Flow
Before: an operator who wants Jev deciding the tier gets no working
automodelclassifier_type: jevundercomplexity_router_configon anautomodel and start the proxyautois missing"model": "auto"and a user messageYou passed in model=auto. There are no healthy deployments for this modelAfter: the same config classifies through Jev and the spend log explains each pick
classifier_type: jevandjev_classifier_config: {model: jev-latest}undercomplexity_router_config, setTYPESAFE_API_KEY, and start the proxyautonext to the three Claude models"model": "auto"and a user messagecause: jev_classifier,classifier_model: typesafe/jev-1.13.0, per tier probabilities, a confidence value, and the classifier costjev_classifier_config.api_keyorapi_base(422), so the proxy's TypeSafe key never leaves TypeSafe's hostRelevant issues
Follows #41607 (merged), which shipped the
typesafe/jev-*registry rows this PR reads forclassifier_cost; that merge is on this branch, so the cost populates in the proof belowAffected release
Linear ticket
Resolves LIT-7975
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
Both legs boot the proxy from their own worktree with
--num_workers 2 --use_v2_migration_resolver, each against its own Postgres database, with real Anthropic models and the real https://api.typesafe.ai (TYPESAFE_API_KEYfrom the environment). Requests use a virtual key fromPOST /key/generate. The After proxy also carriesauto-db, a second auto router stored in the database by an earlier dry run of the same config, which is why/v1/modelslists it too. Shared config:The two prompts:
What is the capital of France? Answer in one word.andDesign a distributed rate limiter that stays correct across three regions with clock skew of up to 200ms. Prove an upper bound on over-admission per window and give the full algorithm with its failure modes., both sent as{"model": "auto", "messages": [{"role": "user", "content": "<prompt>"}], "max_tokens": 120}Before (4b368bf)
Chat completions through
autocurl -sS http://127.0.0.1:32590/v1/models -H "Authorization: Bearer $VIRTUAL_KEY" | jq -c "[.data[].id]"curl -sS -i -X POST http://127.0.0.1:32590/v1/chat/completions -H "Authorization: Bearer $VIRTUAL_KEY" -H "Content-Type: application/json" -d '<prompt 1>'Same for prompt 2
http://127.0.0.1:32590/ui/?page=logs shows the two rows as 400s with no routing decision
Team member pointing the Jev call at a host they choose
classifier_type: jevto configure. The pre-fix behavior of this PR's own commit 0a66328 is shown under After for contrastBoot with
jev_classifier_config.api_baseand noapi_keyclassifier_type: jev, so theautodeployment is skipped at boot and/v1/modelsnever lists itAfter (42f2978, with 853fd04 on top: a test-only commit that cannot change behavior)
Chat completions through
autocurl -sS http://127.0.0.1:29336/v1/models -H "Authorization: Bearer $VIRTUAL_KEY" | jq -c "[.data[].id]"curl -sS -i -X POST http://127.0.0.1:29336/v1/chat/completions -H "Authorization: Bearer $VIRTUAL_KEY" -H "Content-Type: application/json" -d '<prompt 1>'Same for prompt 2
curl -sS "http://127.0.0.1:29336/spend/logs?request_id=<call id>" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[0] | {model, spend, prompt_tokens, completion_tokens, routing: (.metadata.routing_decision | {tier, cause, routed_model, classifier_model, classifier_confidence, classifier_probabilities, classifier_cost})}'The same two prompts against the proxy rebooted with the config also carrying
api_base: https://api.typesafe.aiandapi_key: os.environ/TYPESAFE_API_KEYunderjev_classifier_config(the documented way to pin the host): 200 and 200, call ids627e538c-a441-4a46-be0c-7426cdf726a7(claude-haiku-4-5,classifier_model: typesafe/jev-1.13.0,classifier_cost: 0.000017976) andea05a2da-82a7-48ec-bbb2-36d05df4ab54(claude-sonnet-5, 6204 completion tokens, spend 0.062178)http://127.0.0.1:29336/ui/?page=logs shows both rows as Success at $0.000039 and $0.051648, and the drawer of the second row shows Routing
auto (Auto-Router v2), Tier COMPLEX, Decided by jev_classifier, Routed to claude-sonnet-5, thejev-confidence=0.520000signal and the per tier probability chipsTeam member pointing the Jev call at a host they choose
The member's host is a plain HTTP server that journals each request's method, path, whether the
Authorizationbearer equals the proxy'sTYPESAFE_API_KEY, and thestateit received, and answers like Jev withmodel: attacker-hostAt 0a66328 (before the fix), a team member's key calls
POST http://127.0.0.1:29336/auto_router/test_routingwith"complexity_router_config": {..., "classifier_type": "jev", "jev_classifier_config": {"api_base": "http://127.0.0.1:24058"}}and the member's host journal gains
At 7f581f6 (after the first fix, before the second), the master key with
"jev_classifier_config": {"api_base": "http://127.0.0.1:24058", "api_key": ""}still getsHTTP 200withclassifier_model: typesafe/attacker-hostand the journal gains the same line; the member getsHTTP 400 {"detail":"Invalid member auto-router configuration at api_key."}At the tip, the member request from step 1
At the tip, the blank key request from step 2, with the master key and with the member key alike
The member's host journal did not grow during steps 3 and 4, and the same member's supported shape,
"jev_classifier_config": {"model": "jev-latest"}, still getsHTTP 200withcause: jev_classifierandclassifier_model: typesafe/jev-1.13.0Boot with
jev_classifier_config.api_baseand noapi_keyBoot the tip with the shared config plus
api_base: https://api.typesafe.aiunderjev_classifier_configand noapi_key. The proxy log readsso the proxy comes up without
auto, the same way it treats any other invalid deployment (the log redactor blanks the wordapi_keyin the message)Observations from the run:
jev_classifier: PR causesmax_tokensceiling overrides the caller'smax_tokens: 120: PR leaves aloneautoat boot, startup continues: PR leaves aloneType
🆕 New Feature
Caveats (if any)
High
api_basepaired with the environment key sentTYPESAFE_API_KEYto that host, and a blankapi_keyslipped past the first fixapi_baserequires an explicitapi_key, a blankapi_keyis rejected, and team members can set neither; operators pin the host withapi_baseplusapi_key: os.environ/TYPESAFE_API_KEYapi_basefor team members only (the master key path would still leak), or droppingapi_basefrom the config entirely (TYPESAFE_API_BASEwould then be the only way to reach a private endpoint)Low
POST /auto_router/test_routingdoes not count the Jev call against the caller's budget (a dry run costs about $0.00002 at TypeSafe)jev_classifier_configdropsautoat boot and startup continues, the existing behavior for any invalid deploymentclassifier_modeland the classifier cost use the versioned model TypeSafe reports (jev-1.13.0), not the configuredjev-latestalias, the same choice feat(proxy): add TypeSafe AI Jev evaluate passthrough with registry-priced spend tracking #41607 makes for the passthrough logs; pricing off the alias was the alternative, and both registry rows carry the same price todayFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/b8ce9e101c644969abb9bcc9dfa2c333
Open in Devin Desktop: https://app.devin.ai/desktop/session/b8ce9e101c644969abb9bcc9dfa2c333?variant=devin
Requested by: @mateo-berri
Note
Medium Risk
Changes live request routing and sends prompts to an external classifier using TypeSafe credentials; misconfiguration or classifier outages affect tier picks and spend, though fallbacks and tightened api_base/api_key rules limit credential exfiltration.
Overview
Adds
classifier_type: jevto the complexity auto-router so tier selection can go through TypeSafe’s/v1/systemonestructured choice API instead of parsing LLM text.Operators configure
jev_classifier_config(model, timeout, instructions, circuit breaker). Tier labels or customtier_definitionsbecome the Jev choice criteria; the returned tier drives routing. Failures, timeouts, and an open circuit reuse the existing classifier fallback path. Routing logs gaincause: jev_classifier,classifier_probabilities,classifier_confidence, classifier model/cost, and related OpenAPI/schema fields.Credential rules: the proxy resolves
TYPESAFE_API_KEY/TYPESAFE_API_BASE(defaulthttps://api.typesafe.ai).api_basewithout an explicitapi_keyis rejected so the env key is not sent to arbitrary hosts. Team members editing auto-routers may tune Jev settings but cannot setapi_keyorapi_basevia_MemberJevClassifierConfigvalidation.Reviewed by Cursor Bugbot for commit 853fd04. Bugbot is set up for automated code reviews on this repo. Configure here.