Skip to content

feat(proxy): serve the auto-router preset catalog at runtime - #39412

Merged
tin-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_autorouter_presets_runtime
Sep 3, 2026
Merged

tin-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_autorouter_presets_runtime

Conversation

@tin-berri

@tin-berri tin-berri commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

How it solves it:

  • Catalog moves to litellm/proxy/public_endpoints/autorouter_presets.json, served by GET /public/autorouter_presets
  • The proxy resolves the catalog once per process from GitHub raw on main (shared async client, single flight, bundled fallback), mirroring the model cost map
  • The dashboard fetches it at runtime and keeps no local copy

User Flow

Before: a proxy admin opening the Add Auto Router dialog sees whatever preset catalog was baked into their build, and a preset update published after that build never reaches them

  1. Admin opens https://litellm-domain/ui/ and goes to Models + Endpoints, Auto-Routers, Add Auto Router
  2. The Template dropdown lists the presets frozen into the dashboard bundle at build time
  3. A preset update merged upstream changes nothing for them until they deploy a release containing a rebuilt dashboard

After: the same dialog renders the current published catalog

  1. Admin opens the same dialog on the same proxy version
  2. The Template dropdown lists the catalog the proxy serves at GET /public/autorouter_presets, resolved from the published catalog on main when the proxy started
  3. A preset update merged upstream shows up on the next proxy restart, the way a cost map update does, with no wheel or dashboard rebuild; if the proxy cannot reach the remote it serves its bundled catalog and everything keeps working

Relevant issues

  • Serve the auto-router preset catalog from the proxy at runtime instead of baking it into the dashboard bundle
  • Preset updates then propagate to running deployments the way model cost map updates do

Linear ticket

Resolves LIT-6764

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)

Screenshots / Proof of Fix

Rig: proxy from this tree on port 4620/4000, config with one model and a master key, no DB. The remote leg serves a modified copy of the catalog (relabeled anthropic_family, plus a brand_new_preset carrying an icon field and a future_knob config key this proxy version does not declare) from http://127.0.0.1:4621/autorouter_presets.json

Before (2ade3e1)

The catalog is baked into the bundle and no runtime route serves it

  1. curl -s "http://127.0.0.1:4620/ui/_next/static/chunks/1ffshjz5d4_3s.js" | grep -o anthropic_family | head -1 returns anthropic_family; the catalog ships inside the served JS chunk
  2. curl -H "Authorization: Bearer sk-rig-1234" http://127.0.0.1:4620/public/autorouter_presets returns 404 (as do /get/autorouter_presets and /autorouter/presets)

A catalog change cannot reach a running deployment

  1. There is no URL to update; editing the JSON only takes effect through a dashboard rebuild and artifacts refresh

After (a36254d)

The catalog is served at runtime

  1. curl http://127.0.0.1:4000/public/autorouter_presets returns 200 with anthropic_family, gemini_family, lite, openai_family
  2. The default remote URL points at this file on main, which 404s until this PR merges; three consecutive requests all return 200 from the bundled copy while the log shows exactly one fetch attempt, so a cold cache with a dead upstream costs one outbound request per process, not one per caller

A catalog change reaches a running deployment with no rebuild

  1. Restart the proxy with LITELLM_AUTOROUTER_PRESETS_URL=http://127.0.0.1:4621/autorouter_presets.json pointing at the modified catalog
  2. curl http://127.0.0.1:4000/public/autorouter_presets now returns brand_new_preset and the relabeled Anthropic Family (updated remotely), with the undeclared icon and future_knob fields passed through verbatim; the envelope and the four built-in tier lists validate (tiers accepts exactly those four names), everything else keeps unknown fields (extra="allow"), so a future catalog survives an old proxy unless it carries presets this dashboard could not apply, which reject in favor of the bundled catalog
  3. Republish the same catalog with REASONING deleted from the new preset's tiers and restart: the whole catalog rejects and the four bundled presets serve, so the dialog never receives a preset it cannot apply
  4. The dashboard dev server (npm run dev, http://localhost:3000, Models + Endpoints, Auto-Routers, Add Auto Router) renders both in the Template dropdown: "Anthropic Family (updated remotely)" and "Brand New Preset, added on the remote after this proxy shipped". Screenshot below

UI steps to reproduce the screenshot

  1. Run the proxy from this branch on port 4000, npm run dev in ui/litellm-dashboard
  2. Serve any edited copy of the catalog somewhere and set LITELLM_AUTOROUTER_PRESETS_URL to it before starting the proxy
  3. Open http://localhost:3000, Models + Endpoints, Auto-Routers tab, Add Auto Router, open the Template dropdown

Type

🆕 New Feature

Caveats (if any)

Medium

  • A newer dashboard against an older proxy shows only Custom Configuration plus a retry hint (no bundled UI fallback, same trade /public/complexity_router/scorer_defaults shipped)

Low

  • Until this merges to main the default remote URL 404s; every proxy serves the bundled catalog through the fallback, one warning log per uncached fetch
  • A running proxy keeps its catalog for the process lifetime; a restart picks up a newly published one, matching the model cost map
  • The documentation and code-quality CI jobs stay red until docs: document auto-router preset catalog env vars litellm-docs#1143 (documents the two new env vars) merges; the env-keys gate passes locally against that docs branch

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

Note

Low Risk
Public read-only metadata endpoint with bundled fallback; main caveat is older proxies without the route leaving the UI on Custom Configuration only.

Overview
Moves the auto-router template catalog out of the dashboard bundle and serves it from the proxy at GET /public/autorouter_presets, so preset updates can ship without rebuilding the UI.

The proxy adds litellm.autorouter_presets_url (and LITELLM_AUTOROUTER_PRESETS_URL / LITELLM_LOCAL_AUTOROUTER_PRESETS) and resolves the catalog once per process: optional remote fetch with single-flight caching, validation via new AutoRouterPresetRecord types (strict four-tier names, forward-compatible extra fields), and bundled JSON fallback on failure. OpenAPI schema.d.ts is updated for the new route.

The dashboard drops the static getAllPresets / getPresetByKey helpers in favor of hydratePresets, useAutoRouterPresets (24h react-query cache), and getAutoRouterPresets networking. Add Auto Router shows loading, keeps cached templates on refetch errors, and degrades to Custom Configuration with retry when the catalog never loads.

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

@codspeed

codspeed Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_autorouter_presets_runtime (a36254d) with litellm_internal_staging (6c5fb0e)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (3c6b070) during the generation of this report, so 6c5fb0e was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR moves the auto-router preset catalog behind a runtime proxy endpoint and updates the dashboard to query and hydrate that catalog.

  • Adds remote fetching with a one-hour proxy cache and bundled fallback.
  • Adds a public preset response model and generated API schema.
  • Replaces the dashboard's bundled catalog with a React Query hook and fetch-failure UI.
  • Adds proxy and dashboard tests for caching, fallback, unknown fields, and retry behavior.

Confidence Score: 3/5

The PR should not merge until remote fetching uses LiteLLM's configured HTTP infrastructure and runtime catalog entries cannot crash the Add Auto Router dialog.

The new fetch path bypasses shared outbound HTTP configuration, and the intentionally weak proxy schema passes nested data into dashboard code that unconditionally dereferences the expected tier structure.

Files Needing Attention: litellm/proxy/public_endpoints/public_endpoints.py, litellm/types/proxy/public_endpoints/public_endpoints.py, ui/litellm-dashboard/src/lib/autorouter_presets.ts, ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx

Important Files Changed

Filename Overview
litellm/proxy/public_endpoints/public_endpoints.py Adds the runtime fetch, cache, fallback, and public endpoint, but bypasses LiteLLM's shared HTTP client handling.
litellm/types/proxy/public_endpoints/public_endpoints.py Adds a forward-compatible response envelope whose nested config validation is too weak for the dashboard's unguarded consumers.
ui/litellm-dashboard/src/lib/autorouter_presets.ts Replaces the static catalog with shallow runtime hydration while retaining accessors that assume a complete nested tier shape.
ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx Integrates asynchronous presets and retry messaging, but evaluates unvalidated remote configurations during render.
ui/litellm-dashboard/src/app/(dashboard)/hooks/autoRouter/useAutoRouterPresets.ts Adds the React Query catalog hook with matching cache timing; its explanatory comment conflicts with repository policy.
tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py Covers source-tree fallback, caching, envelope rejection, and unknown-field forwarding but not malformed nested tier data.
ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx Adds useful fetch-failure and retry coverage without covering an envelope-valid malformed runtime preset.

Reviews (1): Last reviewed commit: "feat(proxy): serve the auto-router prese..." | Re-trigger Greptile

Comment on lines +483 to +484
async with httpx.AsyncClient(timeout=5) as client:
response: Final = await client.get(url)

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 Shared HTTP configuration bypassed

If outbound HTTPS depends on LiteLLM's configured certificate, proxy, TLS, redirect, or shared transport behavior, this bare httpx.AsyncClient bypasses that configuration. The remote request then falls back to the bundled catalog, leaving the dashboard with stale presets; concurrent cache misses also create redundant clients and upstream requests.

Rule Used: What: Block any PR which writes it's own custom ht... (source)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5af5e21: the fetch now goes through get_async_httpx_client(httpxSpecialProvider.UI), and ensure_async_clients_test passes locally

model_config = ConfigDict(extra="allow")

label: str
description: str

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 Nested preset shape remains unchecked

When a remote or overridden catalog contains an envelope-valid preset with missing or malformed tiers, this mapping accepts it and the dashboard later calls Object.values or models.map on the invalid values while rendering the dialog. One malformed entry therefore crashes the Add Auto Router form instead of being rejected so the bundled catalog can be served.

Knowledge Base Used: Dashboard and enterprise UI

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5af5e21: tiers is now validated per preset, so a malformed catalog rejects and the bundled one serves; pinned by adapter tests

Comment on lines +10 to +11
// running proxy (that is the point of serving it at runtime), so no release-length staleTime.
const options = {

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 Routine implementation comments added

This comment restates the cache duration already expressed by staleTime rather than explaining complex business logic or directing a tool. Similar explanatory comments were added throughout the preset-fetch path, increasing maintenance surface that can drift from the code.

Context Used: CLAUDE.md (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 Author

Choose a reason for hiding this comment

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

Fixed in 5af5e21: removed that comment and the other restating comments across the preset fetch path

@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread litellm/proxy/public_endpoints/public_endpoints.py Outdated
@veria-ai

veria-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@tin-berri
tin-berri force-pushed the litellm_autorouter_presets_runtime branch from a8fdfb2 to 5af5e21 Compare September 2, 2026 22:20
@tin-berri

Copy link
Copy Markdown
Contributor Author

CI is green except documentation and code-quality, which fail only on the env-keys doc gate pending BerriAI/litellm-docs#1143. @greptileai can you reply here with a score and why that score?

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Confidence score: 4/5.

The implementation is well-scoped and addresses the runtime-catalog flow end to end: the proxy exposes the public endpoint, performs a single-flight process-lifetime fetch with a bundled fallback, validates the required catalog shape while preserving unknown fields, and the dashboard hydrates the server response with an explicit retry/Custom Configuration fallback. The added tests cover local mode, caching, concurrent cold starts, remote failures, malformed payloads, unknown fields, and remote parsing.

I’m holding at 4/5 because the documentation and code-quality checks are still red on the env-key documentation gate. Based on the PR context, that appears to be an external dependency on BerriAI/litellm-docs#1143 rather than a functional issue in this change; once that documentation change lands and the gates rerun successfully, I’d expect this to be 5/5.

Comment thread litellm/types/proxy/public_endpoints/public_endpoints.py
@tin-berri
tin-berri force-pushed the litellm_autorouter_presets_runtime branch from 5af5e21 to 6e994ef Compare September 2, 2026 22:51
@tin-berri

Copy link
Copy Markdown
Contributor Author

CI is green on 6e994ef except documentation and code-quality, both failing only on the env-keys doc gate pending BerriAI/litellm-docs#1143. @greptileai can you reply here with a score and why that score?

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Confidence score: 4/5.

The implementation is well-scoped and the core behavior is covered: runtime proxy serving, single-flight/process-lifetime caching, bundled fallback, schema validation, unknown-field preservation, local-only mode, and dashboard loading/error states with retry. CI is green for the code and tests. I’m holding back one point because the documentation and code-quality jobs remain blocked by the external env-key documentation gate pending BerriAI/litellm-docs#1143; once that dependency lands and those checks pass, this should be a 5/5.

Comment thread ui/litellm-dashboard/src/lib/autorouter_presets.ts
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai can you reply here with a score and why that score?

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Score: 4/5 (high confidence).

The implementation cleanly addresses the stated problem: the catalog is moved to a package-bundled source, exposed through the new public endpoint, resolved once per process with async single-flight behavior, and safely falls back to the bundled catalog when the remote is unavailable. The Pydantic models enforce the required tier shape while preserving unknown fields for forward compatibility. On the dashboard side, React Query integration, loading/error/retry states, and removal of the build-time catalog are consistent with the intended runtime flow. The added tests cover local mode, caching, concurrent cold starts, fallback behavior, malformed catalogs, unknown fields, and UI fetch states.

I’m not assigning 5/5 because the review is based on the change and reported checks rather than independently running the complete proxy and dashboard suites, and the PR still has the separately noted documentation/CI dependency. I found no blocking correctness or security issues in the reviewed changes.

The dashboard's template picker imported autorouter_presets.json at build time, so every
catalog change needed a dashboard rebuild and artifacts refresh. The catalog now lives in
litellm/proxy/public_endpoints/ and GET /public/autorouter_presets serves it, fetching
litellm.autorouter_presets_url (GitHub raw on main, 1h in-process cache, bundled fallback)
so a merged catalog change propagates to running proxies like the model cost map does.
The dashboard fetches it at runtime via useAutoRouterPresets and keeps no local copy.

Resolves LIT-6764
@tin-berri
tin-berri force-pushed the litellm_autorouter_presets_runtime branch from 6e994ef to a36254d Compare September 2, 2026 23:25
@tin-berri

Copy link
Copy Markdown
Contributor Author

CI is green on a36254d except documentation and code-quality, both failing only on the env-keys doc gate pending BerriAI/litellm-docs#1143. @greptileai can you reply here with a score and why that score?

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Score: 4/5

The implementation is well-scoped and addresses the requested flow end to end:

  • The catalog is moved to the proxy package and exposed through /public/autorouter_presets.
  • Remote resolution is performed once per process with a shared client, single-flight locking, and a bundled fallback.
  • The schema validates the fields the dashboard must apply, rejects incompatible tier shapes, and preserves unknown future fields.
  • The dashboard now hydrates the runtime response and handles loading, cached-refetch failures, and first-load failures with retry behavior.
  • The added backend and frontend tests cover caching, concurrency, fallback, validation, passthrough fields, and UI states.

The score is not 5/5 only because the documentation/code-quality gates are still red pending the external litellm-docs#1143 change; the implementation-specific CI reported here is green, so I have no code-blocking finding.

@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 a36254d. Configure here.

@tin-berri

Copy link
Copy Markdown
Contributor Author

Bugbot is clean on a36254d with no open threads. Greptile has not re-scored after three asks; every finding from its 3/5 is fixed and answered

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptile review

@devin-ai-integration

Copy link
Copy Markdown
Contributor

QA'd E2E at a36254d against a live proxy (Postgres, store_model_in_db) plus the dashboard dev server, with LITELLM_AUTOROUTER_PRESETS_URL pointed at a local catalog carrying a relabeled preset and a brand new one. Unit tests pass (44 python, 139 vitest)

Proxy: GET /public/autorouter_presets works without auth, 10 concurrent cold requests produce exactly one upstream fetch, the default GitHub URL 404s pre merge and falls back to the bundled catalog with one warning, a schema invalid remote also falls back, and LITELLM_LOCAL_AUTOROUTER_PRESETS=True skips the fetch entirely. Unknown fields (icon, future_knob) pass through verbatim

UI: the Template dropdown renders the live catalog, a router created from the new preset serves 200s, and after editing the catalog and restarting the proxy the existing router keeps its stored tiers while the dialog shows the updated preset. With the proxy down the dropdown falls back to Custom Configuration with a working Retry

Live catalog in Template dropdown

Minor, not blocking: the dashboard needs a full page reload to pick up a new catalog (24h staleTime), the failure state takes about 15s to show while react-query retries, and after a failure the templates Retry and the models Retry are separate so presets stay disabled until both are clicked

Written by Devin

@tin-berri
tin-berri enabled auto-merge (squash) September 3, 2026 00:43
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.

2 participants