Skip to content

feat(proxy): add TypeSafe AI Jev evaluate passthrough with registry-priced spend tracking - #41607

Merged
mateo-berri merged 8 commits into
mainfrom
litellm_typesafe_passthrough
Sep 17, 2026
Merged

mateo-berri merged 8 commits into
mainfrom
litellm_typesafe_passthrough

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Jev (TypeSafe AI) has no path through the gateway
  • Its evaluate API is not chat completions, so no provider fits
  • Teams cannot use a virtual key or track Jev spend

How it solves it:

User Flow

Before: a developer who wants Jev's structured decisions behind their gateway key has nowhere to send the request

  1. The proxy admin sets TYPESAFE_API_KEY and restarts the proxy
  2. The developer sends POST https://litellm-domain/typesafe/v1/systemone with their virtual key and {"state": "...", "model": "jev-latest", "questions": {...}}
  3. They get a 404 {"detail":"Not Found"}, the route does not exist
  4. They fall back to calling api.typesafe.ai directly with the raw TypeSafe key, outside key auth, budgets, and spend logs

After: the same request goes through the gateway and shows up in spend logs

  1. The proxy admin sets TYPESAFE_API_KEY (and optionally TYPESAFE_API_BASE) and restarts the proxy
  2. The developer sends POST https://litellm-domain/typesafe/v1/systemone with their virtual key and the same body
  3. They get TypeSafe's 200 response unchanged: model, answers with choice, probabilities, confidence, and usage
  4. https://litellm-domain/ui/?page=logs shows the request as typesafe/jev-1.13.0 with the input token count and spend at the registry input price

Relevant issues

Affected release

Linear ticket

Resolves LIT-7975

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. 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
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to 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_resolver booted from the named commit, one local Postgres database per leg, the live api.typesafe.ai upstream (real spend), and a virtual key from POST /key/generate with max_budget: 1. Same curls in the same order on both sides

Pricing 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", aliases jev-latest -> jev-1.13.0 and jev-preview -> jev-1.13.0

Before (87650bf)

$ curl -sS -i -X POST http://127.0.0.1:27514/typesafe/v1/systemone -H "Authorization: Bearer $VIRTUAL_KEY" -H "Content-Type: application/json" -d '{"state":"Help! My payouts have been failing for 3 days.","model":"jev-latest","questions":{"department":{"type":"choice","instructions":"Which team should handle this?","criteria":{"billing":"Payments and invoices","technical":"Bugs, outages, integration errors","sales":"Pricing and plans"}}}}'
HTTP/1.1 404 Not Found
{"detail":"Not Found"}

$ curl -sS -i http://127.0.0.1:27514/typesafe/v1/models -H "Authorization: Bearer $VIRTUAL_KEY"
HTTP/1.1 404 Not Found
{"detail":"Not Found"}

$ curl -sS -i -X POST http://127.0.0.1:27514/typesafe/v1/systemone -H "Content-Type: application/json" -d '<same body>'
HTTP/1.1 404 Not Found
{"detail":"Not Found"}

The logs page has nothing to show for these calls:

Logs page at the merge base, no TypeSafe rows

After (1feaa48)

$ curl -sS -i -X POST http://127.0.0.1:21879/typesafe/v1/systemone -H "Authorization: Bearer $VIRTUAL_KEY" -H "Content-Type: application/json" -d '{"state":"Help! My payouts have been failing for 3 days.","model":"jev-latest","questions":{"department":{"type":"choice","instructions":"Which team should handle this?","criteria":{"billing":"Payments and invoices","technical":"Bugs, outages, integration errors","sales":"Pricing and plans"}}}}'
HTTP/1.1 200 OK
x-litellm-call-id: b88df75a-4be9-4138-adc5-2b651d7320bf
x-litellm-model-api-base: https://api.typesafe.ai/v1/systemone
{"model":"jev-1.13.0","answers":{"department":{"type":"choice","choice":"billing","confidence":0.73,"probabilities":{"technical":0.18,"billing":0.82,"sales":0.0}}},"usage":{"input_tokens":342,"output_tokens":38}}

$ curl -sS -i http://127.0.0.1:21879/typesafe/v1/models -H "Authorization: Bearer $VIRTUAL_KEY"
HTTP/1.1 200 OK
x-litellm-call-id: 8ad333ff-bb74-4871-b6f3-c669dc3292b8
{"models":[{"name":"jev-latest","description":"The latest iteration of TypeSafe's System One Model: Jev","release_date":"2026-09-10T18:38:01.391457+00:00"},{"name":"jev-preview","description":"A preview version of `jev-latest`: should be better in most ways","release_date":"2026-09-10T18:39:06.057655+00:00"}]}

$ curl -sS -i -X POST http://127.0.0.1:21879/typesafe/v1/systemone -H "Content-Type: application/json" -d '<same body>'
HTTP/1.1 401 Unauthorized
{"error":{"message":"Authentication Error, No api key passed in.","type":"auth_error","param":"None","code":"401"}}

Spend rows, read back through the spend API:

$ curl -sS "http://127.0.0.1:21879/spend/logs?request_id=b88df75a-4be9-4138-adc5-2b651d7320bf" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq '[.[] | {model, custom_llm_provider, prompt_tokens, completion_tokens, total_tokens, spend, api_key_alias: .metadata.user_api_key_alias}]'
[
  {
    "model": "typesafe/jev-1.13.0",
    "custom_llm_provider": "typesafe",
    "prompt_tokens": 342,
    "completion_tokens": 38,
    "total_tokens": 380,
    "spend": 0.000014364,
    "api_key_alias": "jev-qa-tip"
  }
]

$ curl -sS "http://127.0.0.1:21879/spend/logs?request_id=8ad333ff-bb74-4871-b6f3-c669dc3292b8" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq '[.[] | {model, custom_llm_provider, prompt_tokens, completion_tokens, spend}]'
[
  {
    "model": "typesafe/unknown",
    "custom_llm_provider": "typesafe",
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "spend": 0.0
  }
]

342 * 4.2e-8 + 38 * 0 = 0.000014364, logged under the versioned model from the response rather than the jev-latest alias from the request

Logs page at the tip, the systemone call priced as typesafe/jev-1.13.0

Log drawer at the tip, token counts and spend for the call

Dependents of the registry rows and the new route, driven on both sides with the same script:

Path 87650bf 1feaa48
POST /model/new with typesafe/jev-latest in model_list 500 500
GET /v1/models after that not listed not listed
POST /v1/chat/completions on that model 400 invalid model 400 invalid model
GET /health?model=<it> 0 healthy, 0 unhealthy 0 healthy, 0 unhealthy
GET /model_group/info?model_group=<it> [] []
GET /public/model_hub [] 200 [] 200
key with allowed_routes: ["/typesafe/*"], GET /typesafe/v1/models 404 200
same key, GET /v1/models 403 403
POST /model/delete 200 200

Observations from the run:

  • Models listing logs typesafe/unknown, zero tokens: PR causes
  • POST /model/new with typesafe/jev-latest answers 500: PR leaves alone
  • x-litellm-model-api-base names the upstream URL: PR causes

Not verified: TYPESAFE_API_BASE pointing 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

  • A call whose response names no model (GET /typesafe/v1/models) is logged as typesafe/unknown with 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 call
  • The typesafe/* registry rows carry pricing only. POST /model/new with typesafe/jev-latest as a model_list entry 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 here
  • mode: "evaluation" is a new registry mode added to the ModelInfoBase literal and the JSON schema. /model_group/info, /public/model_hub, and /v1/models never see it because no router entry can load these rows
  • Only TYPESAFE_API_KEY or 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 needs
  • The non-required osv-scan check fails on a soupsieve advisory (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 it

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

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.ai directly.

The proxy exposes GET/POST /typesafe/{endpoint:path}, forwards to TYPESAFE_API_BASE (default https://api.typesafe.ai) with TYPESAFE_API_KEY on the upstream Authorization header, and registers the path in gateway allowlists, lazy passthrough loading, and OpenAPI/UI schema.

Spend logging parses TypeSafe responses for model and usage.input_tokens / output_tokens, prices input from the model registry (output cost is zero for the new typesafe/jev-* entries), and logs calls as typesafe/{model} with fallback to the request model or unknown. Registry and schema gain a new evaluation model mode for these entries.

Reviewed by Cursor Bugbot for commit 1feaa48. Bugbot is set up for automated code reviews on this repo. Configure here.

mateo-berri and others added 2 commits September 17, 2026 15:53
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-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@codspeed

codspeed Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_typesafe_passthrough (1feaa48) with main (356b8d4)

Open in CodSpeed

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@greptile-apps

greptile-apps Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge with no outstanding correctness, security, or repository-rule violations.

Findings

  1. P1 Schema Rejects Evaluation Mode ▶
  2. P1 POST Query Parameters Dropped ▶
  3. P2 Provider Code Outside llms ▶

Summary

Adds a TypeSafe AI Jev passthrough backed by virtual-key authentication and registry-priced spend tracking.

  • Registers authenticated GET and POST routes under /typesafe/{endpoint}.
  • Injects configured TypeSafe credentials while preserving the shared passthrough request path.
  • Normalizes Jev usage and model metadata for standard spend logging.
  • Adds input-only registry pricing for versioned, latest, and preview Jev models.
  • Updates generated API schemas and adds route, pricing, logging, and metadata tests.

Reviews (7) · Last reviewed commit: "fix(proxy): log TypeSafe calls that name..."

"typesafe/jev-1.13.0": {
"input_cost_per_token": 4.2e-08,
"litellm_provider": "typesafe",
"mode": "evaluation",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Schema Rejects Evaluation Mode

The new evaluation value is absent from the committed schema, so the required cost-map guard fails until it is regenerated

Comment on lines +528 to +562
@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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Provider Code Outside llms

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!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeSafe pass-through routes intentionally use the shared proxy passthrough layer; moving dispatch into llms would break established provider passthrough architecture.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shared passthrough routing and success handlers intentionally live under proxy; provider-specific completion adapters cannot replace this endpoint forwarding and spend-log integration.

@codecov

codecov Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.15068% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...r_handlers/typesafe_passthrough_logging_handler.py 90.90% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

mateo-berri and others added 2 commits September 17, 2026 16:20
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>
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

2 similar comments
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py Outdated
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

Comment on lines +544 to +546
updated_url: Final = base_url.copy_with(
path=HttpPassThroughEndpointHelpers.join_base_and_endpoint_path(base_url, normalized_endpoint),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 POST Query Parameters Dropped

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@mateo-berri mateo-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mateo-berri
mateo-berri merged commit deb9d8a into main Sep 17, 2026
89 of 90 checks passed
@mateo-berri
mateo-berri deleted the litellm_typesafe_passthrough branch September 17, 2026 23:37
Setsuna-Yukirin pushed a commit to Setsuna-Yukirin/litellm that referenced this pull request Sep 18, 2026
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
mgmonteleone pushed a commit to mgmonteleone/litellm that referenced this pull request Sep 22, 2026
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>
mateo-berri added a commit that referenced this pull request Sep 23, 2026
feat(typesafe): backport #41607 to stable/1.101.x for v1.101.1
mateo-berri added a commit that referenced this pull request Sep 23, 2026
feat(typesafe): backport #41607 to stable/1.100.x for v1.100.2
mateo-berri added a commit that referenced this pull request Sep 23, 2026
feat(typesafe): backport #41607 to stable/1.99.x for v1.99.2
ztsalexey pushed a commit to 2bb-dev/litellm that referenced this pull request Sep 23, 2026
…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>
achraf-mer pushed a commit to achraf-mer/litellm that referenced this pull request Sep 30, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant