Repository navigation
feat(proxy): add TypeSafe AI Jev evaluate passthrough with registry-priced spend tracking - #41607
Conversation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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:
|
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
| "typesafe/jev-1.13.0": { | ||
| "input_cost_per_token": 4.2e-08, | ||
| "litellm_provider": "typesafe", | ||
| "mode": "evaluation", |
| @router.api_route( | ||
| "/typesafe/{endpoint:path}", | ||
| methods=["GET", "POST"], | ||
| tags=["TypeSafe AI Pass-through", "pass-through"], | ||
| ) | ||
| async def typesafe_proxy_route( | ||
| endpoint: str, | ||
| request: Request, | ||
| fastapi_response: Response, | ||
| user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), | ||
| ): | ||
| """[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)""" | ||
| base_target_url: Final = get_secret_str("TYPESAFE_API_BASE") or "https://api.typesafe.ai" | ||
| encoded_endpoint: Final = httpx.URL(endpoint).path | ||
| normalized_endpoint: Final = encoded_endpoint if encoded_endpoint.startswith("/") else f"/{encoded_endpoint}" | ||
| base_url: Final = httpx.URL(base_target_url) | ||
| updated_url: Final = base_url.copy_with( | ||
| path=HttpPassThroughEndpointHelpers.join_base_and_endpoint_path(base_url, normalized_endpoint), | ||
| params=request.query_params, | ||
| ) | ||
| typesafe_api_key: Final = passthrough_endpoint_router.get_credentials( | ||
| custom_llm_provider="typesafe", | ||
| region_name=None, | ||
| ) | ||
| endpoint_func: Final = create_pass_through_route( | ||
| endpoint=endpoint, | ||
| target=str(updated_url), | ||
| custom_headers={ | ||
| "Authorization": f"Bearer {typesafe_api_key}", | ||
| "Content-Type": "application/json", | ||
| }, | ||
| custom_llm_provider="typesafe", | ||
| is_streaming_request=False, | ||
| ) | ||
| return await endpoint_func(request, fastapi_response, user_api_key_dict) |
There was a problem hiding this comment.
This route and its logging dispatch add TypeSafe-specific behavior outside llms/, violating the repository isolation requirement that must be satisfied before merge
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)
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.
TypeSafe pass-through routes intentionally use the shared proxy passthrough layer; moving dispatch into llms would break established provider passthrough architecture.
There was a problem hiding this comment.
That’s fair. The route delegates to the shared passthrough machinery, and the TypeSafe-specific logging handler is placed alongside the existing provider passthrough handlers under proxy/pass_through_endpoints/llm_provider_handlers. This is proxy integration glue rather than provider implementation that belongs in llms/; moving it there would break the established passthrough architecture. I’m withdrawing this finding.
There was a problem hiding this comment.
Shared passthrough routing and success handlers intentionally live under proxy; provider-specific completion adapters cannot replace this endpoint forwarding and spend-log integration.
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>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2 similar comments
|
bugbot run |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
| updated_url: Final = base_url.copy_with( | ||
| path=HttpPassThroughEndpointHelpers.join_base_and_endpoint_path(base_url, normalized_endpoint), | ||
| ) |
There was a problem hiding this comment.
For TypeSafe JSON POST requests such as /typesafe/v1/systemone?trace=yes, this URL construction no longer includes the incoming query string. The shared JSON passthrough path only reads query parameters from the request body's query_params field, so normal URL query parameters are omitted from the upstream request. The updated test hides this behavior by merging request.query_params inside its fake endpoint rather than exercising the production forwarding path.
There was a problem hiding this comment.
The shared passthrough forwards request.query_params itself (pass_through_endpoints.py line 994, then params= on the JSON POST build_request), so removing them from the target avoids duplication.
There was a problem hiding this comment.
You're right. The shared JSON passthrough adds request.query_params as the params= argument when building the upstream request, so keeping the query string out of target avoids duplication while still forwarding it. The test's copy_merge_params() models that shared behavior; it is not masking a production omission. This comment is incorrect and can be dismissed.
|
bugbot run |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
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 1feaa48. Configure here.
BerriAI#41607 registered the typesafe pass-through with a route that only accepted GET and POST, so a PUT, DELETE or PATCH to /typesafe/... came back 405 before reaching the upstream. CircleCI's pass-through method test caught it, but that lane does not run on the PR gate, so the mapped unit test now covers the same invariant for typesafe The same CircleCI run also failed test_models_by_provider because typesafe is not a key of models_by_provider. Registering it there would satisfy the assertion without changing behaviour: typesafe has no LlmProviders member, so a typesafe/* deployment never loads and get_valid_models returns nothing, and its spend is priced straight from model_cost. The test already skips search-mode providers for that reason, so it now skips evaluation mode too
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>
feat(typesafe): backport #41607 to stable/1.101.x for v1.101.1
feat(typesafe): backport #41607 to stable/1.100.x for v1.100.2
feat(typesafe): backport #41607 to stable/1.99.x for v1.99.2
…acking Backport of BerriAI#41607 to stable/1.102.x. Cherry-picked from deb9d8a (main). Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…acking Backport of BerriAI#41607 to stable/1.99.x. Cherry-picked from deb9d8a (main). Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
TLDR
Problem this solves:
How it solves it:
/typesafe/{endpoint}passthrough toapi.typesafe.aiTYPESAFE_API_KEY, callers use virtual keysjev-1.13.0,jev-latest,jev-previewUser Flow
Before: a developer who wants Jev's structured decisions behind their gateway key has nowhere to send the request
TYPESAFE_API_KEYand restarts the proxy{"state": "...", "model": "jev-latest", "questions": {...}}{"detail":"Not Found"}, the route does not existAfter: the same request goes through the gateway and shows up in spend logs
TYPESAFE_API_KEY(and optionallyTYPESAFE_API_BASE) and restarts the proxymodel,answerswithchoice,probabilities,confidence, andusagetypesafe/jev-1.13.0with the input token count and spend at the registry input priceRelevant issues
Affected 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
Topology on both legs:
proxy_cli.py --num_workers 2 --use_v2_migration_resolverbooted from the named commit, one local Postgres database per leg, the liveapi.typesafe.aiupstream (real spend), and a virtual key fromPOST /key/generatewithmax_budget: 1. Same curls in the same order on both sidesPricing evidence, parsed from https://docs.typesafe.ai/models.md on 2026-09-17:
Jev 1.13 | jev-1.13.0 | Price (per Btok / per Mtok) | $42 / $0.042, "Charged per input token. Output tokens are free", aliasesjev-latest -> jev-1.13.0andjev-preview -> jev-1.13.0Before (87650bf)
The logs page has nothing to show for these calls:
After (1feaa48)
Spend rows, read back through the spend API:
342 * 4.2e-8 + 38 * 0 = 0.000014364, logged under the versioned model from the response rather than thejev-latestalias from the requestDependents of the registry rows and the new route, driven on both sides with the same script:
POST /model/newwithtypesafe/jev-latestinmodel_listGET /v1/modelsafter thatPOST /v1/chat/completionson that modelGET /health?model=<it>GET /model_group/info?model_group=<it>[][]GET /public/model_hub[]200[]200allowed_routes: ["/typesafe/*"],GET /typesafe/v1/modelsGET /v1/modelsPOST /model/deleteObservations from the run:
typesafe/unknown, zero tokens: PR causesPOST /model/newwithtypesafe/jev-latestanswers 500: PR leaves alonex-litellm-model-api-basenames the upstream URL: PR causesNot verified:
TYPESAFE_API_BASEpointing anywhere but the default host, a passthrough deployment credential instead of the env key, and a TypeSafe 4xx or 5xx flowing back through the route (the live API answered 200 to everything sent)Type
🆕 New Feature
Caveats (if any)
Low
GET /typesafe/v1/models) is logged astypesafe/unknownwith zero tokens and zero spend, the same label the Anthropic and Gemini passthrough handlers use for that case. Alternatives considered: skip the spend row for such calls (loses the record of who used the route) or label it by request path (a label shape no dashboard reads). Fixing it is net-negative: it would add a third convention for one free calltypesafe/*registry rows carry pricing only.POST /model/newwithtypesafe/jev-latestas amodel_listentry answers 500 on both base and tip, so Jev is reachable through/typesafe/{endpoint}and nothing else. Pre-existing for any provider without a chat implementation, left alone heremode: "evaluation"is a new registry mode added to theModelInfoBaseliteral and the JSON schema./model_group/info,/public/model_hub, and/v1/modelsnever see it because no router entry can load these rowsTYPESAFE_API_KEYor a passthrough deployment credential reaches TypeSafe; the caller's own header is replaced, never forwarded, which is what makes virtual keys the only credential a client needsosv-scancheck fails on asoupsieveadvisory (GHSA-gjv8-xp57-g29c, GHSA-j934-xhv5-fg8f) in a dependency this PR does not touch; build(deps): bump soupsieve from 2.8.4 to 2.9.2 #41679 bumps itFinal 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
Low Risk
Additive passthrough and logging path; upstream auth uses configured credentials only, with no changes to core chat routing or existing providers.
Overview
Adds TypeSafe AI (Jev) as a first-class proxy passthrough so clients can call evaluate endpoints with virtual keys instead of hitting
api.typesafe.aidirectly.The proxy exposes
GET/POST /typesafe/{endpoint:path}, forwards toTYPESAFE_API_BASE(defaulthttps://api.typesafe.ai) withTYPESAFE_API_KEYon the upstreamAuthorizationheader, and registers the path in gateway allowlists, lazy passthrough loading, and OpenAPI/UI schema.Spend logging parses TypeSafe responses for
modelandusage.input_tokens/output_tokens, prices input from the model registry (output cost is zero for the newtypesafe/jev-*entries), and logs calls astypesafe/{model}with fallback to the request model orunknown. Registry and schema gain a newevaluationmodel mode for these entries.Reviewed by Cursor Bugbot for commit 1feaa48. Bugbot is set up for automated code reviews on this repo. Configure here.