Skip to content

feat(router): support default params and expose optional checks in Admin UI - #33144

Open
krrish-berri-2 wants to merge 20 commits into
litellm_internal_stagingfrom
litellm_router_settings_admin_ui
Open

feat(router): support default params and expose optional checks in Admin UI#33144
krrish-berri-2 wants to merge 20 commits into
litellm_internal_stagingfrom
litellm_router_settings_admin_ui

Conversation

@krrish-berri-2

@krrish-berri-2 krrish-berri-2 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Pre-Submission checklist

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

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • 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

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

Tested commit d418752267 through the Admin UI against a DB-backed local proxy and Postgres

The backend precondition contained default_litellm_params.qa_marker = "pr33144_index_fixed_e1e233f" and two cache-control rows. Open Router Settings as a full Admin and confirm Cache Control Injection Points renders those rows while the raw Default LiteLLM Params input remains absent

Change the first row to Role Assistant, type Index -4, increment it to -3, then add a third row with Role User and Index 0. Click Save Changes, expect the success notification, then hard reload and confirm the exact three rows persist

After save, Postgres retained the hidden marker, JSON-null timeout, and max_retries: 0 while persisting the three edited cache-control rows. Optional Pre-call Checks retained prompt_caching, and Reliability & Retries remained rendered

The annotated recording and screenshots are in the QA comment

Type

New Feature

Bug Fix

Changes

Router.get_settings() and Router.update_settings() now round-trip default_litellm_params and optional_pre_call_checks through DB-backed configuration. Default parameters use replacement semantics, removed optional checks are unregistered, automatic budget enforcement remains active when required, and non-admin config responses mask sensitive default values

The Router Settings UI owns the Optional Pre-call Checks contract instead of querying backend metadata to decide whether it renders. Its choices, labels, and documentation link are frontend-defined

The raw Default LiteLLM Params JSON input is intentionally not exposed, so visiting the page cannot prefill or accidentally configure arbitrary defaults. Cache Control Injection Points remains rendered through a dedicated frontend-owned component with frontend-defined roles, structure, copy, and validation

Untouched default_litellm_params is omitted from unrelated saves. Explicit cache-control edits send the updated object, while explicit disabling removes only cache_control_injection_points and preserves unrelated default parameters

The shared cache-control editor remains available to Add Model. Its Ant Design InputNumber supports direct integer typing, negative values, increment and decrement controls, responsive row wrapping, and accessible removal

Focused frontend regressions verify cache-control rendering without the raw JSON input, no mutation on initial render, hidden-value preservation, explicit enable and disable saves, and frontend-owned Optional Pre-call Checks. Backend coverage verifies update and removal behavior, budget enforcement, masking, and config responses

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/7c06b4a26d2e42fe84440cc5538b1d51
Requested by: @krrish-berri-2

…cks in Admin UI

Router.update_settings() silently dropped default_litellm_params and
optional_pre_call_checks (not in the allow-list, and optional_pre_call_checks
was never even stored as a readable attribute), so the Admin UI's Router
Settings page could not display or persist either setting - e.g. enabling
cache_control_injection_points or prompt_caching pre-call routing required
editing config.yaml directly.

Router now persists optional_pre_call_checks and returns both fields from
get_settings(). update_settings() merges default_litellm_params instead of
replacing it (a full replace would drop the timeout/max_retries/metadata
defaults Router.__init__ sets), and diffs optional_pre_call_checks against
what's already applied before calling add_optional_pre_call_checks(), since
that method has no dedup guard for prompt_caching/enforce_model_rate_limits
and would otherwise register a duplicate callback on every re-save.
@CLAassistant

CLAassistant commented Jul 14, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
0 out of 2 committers have signed the CLA.

❌ krrish-berri
❌ krrish-berri-2
You have signed the CLA already but the status is still pending? Let us recheck it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 498aa2997d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread litellm/router.py Outdated
Comment thread ui/litellm-dashboard/src/components/router_settings/index.test.tsx Outdated
@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR exposes default_litellm_params and optional_pre_call_checks in the Admin UI's Router Settings page by wiring both fields through Router.get_settings() / update_settings() and the /get/config/callbacks endpoint, with role-gated masking of sensitive values in default_litellm_params for non-full-admin callers.

  • Backend (router.py): optional_pre_call_checks is now persisted as self.optional_pre_call_checks; update_settings dispatches to _apply_optional_pre_call_checks_setting (diff-and-apply) and _replace_default_litellm_params_setting (full replace, None is a no-op).
  • UI: Two new components (OptionalPreCallChecksSelector, DefaultLitellmParamsSection) plus an extracted shared CacheControlInjectionPointsEditor that also fixes a pre-existing bug in cache_control_settings.tsx where role/index edits read the wrong form field name.

Confidence Score: 5/5

Safe to merge; backend changes are well-guarded and the UI correctly round-trips both new fields.

The core router changes are correctly implemented and well-tested. Both findings are non-blocking quality observations that do not affect the correctness of the stated fix.

No files require special attention.

Important Files Changed

Filename Overview
litellm/router.py Adds optional_pre_call_checks persistence and update/remove handlers; wires both new fields into get_settings()/update_settings(). Affinity callback removal only disables flags, does not unregister the callback object.
litellm/proxy/proxy_server.py Adds _apply_router_settings_role_gate to mask sensitive values in default_litellm_params for non-admin callers. Correct implementation.
litellm/types/management_endpoints/router_settings_endpoints.py Adds optional_pre_call_checks to ROUTER_SETTINGS_FIELDS with matching options; updates default_litellm_params description and link.
ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.tsx New component for free-form params + cache control toggle. Local otherParamsText state not synced with value prop changes after mount.
ui/litellm-dashboard/src/components/router_settings/OptionalPreCallChecksSelector.tsx New multi-select component for optional pre-call checks driven by server metadata.
ui/litellm-dashboard/src/components/shared/cache_control_injection_points_editor.tsx New shared fully-controlled cache control injection points editor extracted from cache_control_settings.tsx.
ui/litellm-dashboard/src/components/add_model/cache_control_settings.tsx Delegates to shared editor; fixes pre-existing bug where role/index edits read wrong form field name.
ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.tsx Adds both new sections with key-presence guard for default_litellm_params.
tests/test_litellm/proxy/test_proxy_server.py Adds mock-based tests for role-gated masking of default_litellm_params.
tests/test_litellm/test_router.py Formatting-only changes plus new tests for update_settings round-trip. No assertions weakened.
ui/litellm-dashboard/src/lib/http/schema.d.ts Adds default_litellm_params and optional_pre_call_checks to UpdateRouterConfig schema type.

Reviews (6): Last reviewed commit: "fix(ui): surface a visible error when de..." | Re-trigger Greptile

Comment thread litellm/router.py Outdated
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Patch % Lines
litellm/router.py 94.82% 3 Missing ⚠️
litellm/proxy/proxy_server.py 75.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread litellm/router.py
@veria-ai

veria-ai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

PR overview

This PR adds support for default router parameters and surfaces optional check controls in the Admin UI. It also changes router callback handling related to model rate limits and budget limiting.

There is one open security issue remaining after two have been addressed. The remaining concern is that disabling certain controls on one router can remove shared callback enforcement for other routers in the same process, allowing their rate or budget limits to be bypassed under multi-router deployments. The risk is conditional on shared-process router usage and control toggling behavior, but it affects enforcement boundaries between routers.

Open issues (1)

Fixed/addressed: 2 · PR risk: 4/10

@codspeed-hq

codspeed-hq Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_router_settings_admin_ui (e3d0d20) with litellm_internal_staging (a780d4e)

Open in CodSpeed

…multi-select

The Router Settings page rendered optional_pre_call_checks as free-text JSON,
requiring admins to know and correctly type the exact valid check names. Add
a dedicated multi-select populated from the field's known options (already
returned by /router/fields), matching how routing_strategy already gets its
own selector instead of a raw text field.

The value now flows through React state (like routing_strategy/enable_tag_filtering)
instead of the page's DOM-querySelector-based save mechanism, since an antd
Select doesn't produce a plain named <input> for that mechanism to read.
default_litellm_params keeps the raw-JSON editor since it has no fixed set of
keys to offer as options.
… of /get/config/callbacks

default_litellm_params is merged into every completion call's kwargs, so an
operator can put a shared api_key or an Authorization header under
extra_headers there. Router.get_settings() now returns it (needed so the
Admin UI's Router Settings page can display/edit it), but /get/config/callbacks
forwarded router_settings verbatim regardless of caller role - unlike the
callback and alerting env vars on the same response, which already redact for
non-full-admin callers (e.g. PROXY_ADMIN_VIEW_ONLY). That let a read-only
admin read another admin's upstream provider credentials.

Add the same role gate used for callback/alerting env vars, scoped to
default_litellm_params via the existing SensitiveDataMasker so any
key/secret/token/auth-shaped field is masked for non-full-admin callers while
full admins keep seeing the real value.
…s/optional_pre_call_checks

_add_router_settings_from_db_config merges config.yaml router_settings with
the DB router_settings row and calls update_settings(**combined) directly,
without going through UpdateRouterConfig's exclude_none filtering. An
explicit `default_litellm_params: null` or `optional_pre_call_checks: null`
in either source therefore reached the new elif branches verbatim:
`{**dict, **None}` and iterating `None` both raise TypeError, crashing
proxy startup / config sync.
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptileai review

@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@Veria review

Comment thread litellm/router.py Outdated
…omplexity, prettier

- schema.d.ts was stale after adding default_litellm_params/optional_pre_call_checks
  to UpdateRouterConfig; applied the exact diff CI's schema-vs-spec check expects.
- update_settings's two new elif branches pushed its cyclomatic complexity from 14
  to 16, crossing ruff-strict.toml's max-complexity=15 budget. Replaced both branches
  with a single `var in _CUSTOM_UPDATE_SETTINGS_HANDLERS` dispatch (one branch instead
  of two) so adding a custom-handled setting doesn't grow this function's branch count
  per field; also switched the two new helper signatures to `X | None` per UP045.
- prettier --write on the two test files flagged by frontend-lint.
…sted

router_code_coverage.py detects test coverage via a static AST scan for
literal `.method_name(` calls in test files, not real coverage
instrumentation - it flagged _merge_default_litellm_params_setting and
_apply_optional_pre_call_checks_setting as untested even though they're
exercised through update_settings(default_litellm_params=...) /
(optional_pre_call_checks=...) in test_router.py, matching the existing
_merge_tools_from_deployment / _invalidate_access_groups_cache precedent for
private helpers only called indirectly.
update_settings(optional_pre_call_checks=...) only ever unioned incoming
checks into self.optional_pre_call_checks - it never removed anything absent
from the incoming list. Unchecking a check in the Admin UI's new multi-select
and clicking Save silently did nothing live: the DB got the smaller list, but
the router kept the old value and its registered callback (e.g.
PromptCachingDeploymentCheck, RouterBudgetLimiting) active until a restart,
diverging from what the UI showed as saved.

_remove_optional_pre_call_checks mirrors add_optional_pre_call_checks for the
removal direction: clears the relevant flag on the shared DeploymentAffinityCheck
/ EncryptedContentAffinityCheck instance for the affinity-based checks, and
unregisters the dedicated callback (via the existing
logging_callback_manager.remove_callbacks_by_type) for prompt_caching,
enforce_model_rate_limits, and router_budget_limiting. optional_pre_call_checks
is now set to exactly the incoming list rather than a strictly-growing union.
Extend the removal regression tests to exercise enforce_model_rate_limits
(alongside prompt_caching/router_budget_limiting), all three
DeploymentAffinityCheck flags including the loop's skip-non-matching-callback
path (a mixed optional_callbacks list with prompt_caching present), and the
separate EncryptedContentAffinityCheck flag. Also drop the dead
optional_callbacks-is-None guard in _remove_optional_pre_call_checks: it can
never be None by the time removed_checks is non-empty, since anything in
self.optional_pre_call_checks only got there via add_optional_pre_call_checks,
which always initializes the list first.
Comment thread litellm/router.py
…nfigured

Router.__init__ auto-enables router_budget_limiting whenever a deployment has
max_budget/budget_duration set or provider_budget_config is configured
(RouterBudgetLimiting.should_init_router_budget_limiter), independent of what
optional_pre_call_checks explicitly lists. _remove_optional_pre_call_checks
didn't account for that: a save (via the Admin UI's new multi-select, or a
config-sync payload) that simply omitted "router_budget_limiting" from the
list would unregister the RouterBudgetLimiting callback and null out
router_budget_logger, silently letting deployments keep serving requests
after their configured budget is exhausted.

_remove_optional_pre_call_checks now checks should_init_router_budget_limiter
before actually removing the callback for this one check, and returns the
checks it kept active despite being in removed_checks so
_apply_optional_pre_call_checks_setting can fold them back into the tracked
optional_pre_call_checks list - keeping the UI's displayed state honest about
what's still enforced.
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptileai review

…he UI

It's a literal in the OptionalPreCallChecks type union, but
add_optional_pre_call_checks has no handler for it - selecting it from the
Admin UI's new multi-select would save successfully and show as enabled while
the router does nothing with it. Drop it from optional_pre_call_checks'
exposed options until it's actually implemented.
…a merge

update_settings(default_litellm_params=...) merged the incoming dict into the
existing one ({**old, **new}), which can only add or overwrite keys, never
remove one. Since the Admin UI reads and re-submits the entire
default_litellm_params object as a single JSON blob, a merge meant clearing a
field (e.g. removing cache_control_injection_points) by editing it out of the
UI's textarea and saving had no effect: the old key survived the merge and
stayed visible from /get/config/callbacks and active on the live router.

Replace wholesale instead, matching how every other dict-shaped router
setting (e.g. model_group_alias) is already handled via the generic setattr
path - callers are expected to submit the complete desired object, which the
UI already does by round-tripping the full current value.

Also drop explanatory comments/docstrings added earlier in this branch that
weren't requested, per this repo's no-unrequested-comments convention.
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptileai review

@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@Veria review

default_litellm_params.cache_control_injection_points was only editable as
raw JSON on the Router Settings page, requiring an admin to hand-write
[{"location": "message", "role": "system"}] to enable prompt cache routing -
clunky next to the structured Switch + row editor already used for the same
field on the Add Model page.

Extracted the row editor (location/role/index inputs, add/remove) out of
add_model/cache_control_settings.tsx into a form-agnostic shared component,
CacheControlInjectionPointsEditor, driven by plain value/onChange props
instead of antd Form bindings. cache_control_settings.tsx now delegates to it
(and picks up a real fix along the way: its role/index onChange handlers read
form.getFieldValue("cache_control_points"), a field that was never
registered under that name, so those edits silently never synced into
litellm_extra_params - now reads the correct "cache_control_injection_points"
field via Form.useWatch).

New DefaultLitellmParamsSection renders that same editor for Router Settings
plus a JSON textarea for the remaining default_litellm_params keys
(timeout, max_retries, metadata, ...). Fully controlled through React state
(matching the optional_pre_call_checks pattern) rather than the page's
generic DOM-read save path, and excluded from ReliabilityRetriesSection's
raw-JSON rendering so it isn't shown twice.

Could not visually verify in a live browser this session (a chrome-extension
tooling conflict blocked screenshots/typing); verified via 174 passing
frontend tests covering both components and their Add Model / Router
Settings integrations, plus a clean tsc typecheck.
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptileai review

@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@Veria review

…invalid

Invalid JSON typed into the Default LiteLLM Params textarea was silently
swallowed on blur (console.error only, no on-screen indication), so an admin
could think their edit was saved when it was actually discarded. Now flags
the field with an error state and shows a warning notification, and clears
both once the field is edited again.
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptileai review

@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@Veria review

@devin-ai-integration

devin-ai-integration Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

QA update for commit d418752267

Result: passed

The DB-backed proxy returned an existing default_litellm_params object with marker pr33144_index_fixed_e1e233f and two cache-control rows before the UI test

Router Settings rendered Cache Control Injection Points from frontend-owned definitions while keeping the raw Default LiteLLM Params input absent. Optional Pre-call Checks and Reliability & Retries remained visible

I changed row 1 to Role Assistant, typed Index -4, incremented it to -3, then added row 3 as Role User with Index 0. Save Changes succeeded, and a hard reload restored Assistant / -3, System / 3, and User / 0

Postgres retained the hidden marker, JSON-null timeout, and max_retries: 0 while persisting the three edited cache-control rows. prompt_caching also remained selected

Before edit After save and reload
Two persisted cache-control rows before editing Three cache-control rows after save and reload

Annotated recording

Full QA report with screenshots

Devin session

Focused Vitest passed 42 tests. Focused ESLint had 0 errors. Production next build, make pre-commit, and build-ui passed

The remaining OSV failure reports httplib2 0.31.2 and setuptools 82.0.1 from unchanged uv.lock

krrish-berri and others added 3 commits July 14, 2026 13:19
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>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration devin-ai-integration Bot changed the title feat(router): expose default_litellm_params and optional_pre_call_checks in Admin UI feat(router): support default params and expose optional checks in Admin UI Jul 14, 2026
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Comment thread litellm/router.py
retained_checks.append(check)
continue
litellm.logging_callback_manager.remove_callbacks_by_type(optional_callbacks, callback_type)
litellm.logging_callback_manager.remove_callbacks_by_type(litellm.callbacks, callback_type)

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.

Medium: Cross-router security controls are removed

remove_callbacks_by_type removes all matching instances from litellm.callbacks, including callbacks owned by other Router objects. If one router disables enforce_model_rate_limits or router_budget_limiting, users of another router in the same process can exceed that router's configured limits; capture this router's matching callback objects and remove only those exact objects from the global list.

krrish-berri and others added 3 commits July 21, 2026 20:55
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>
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.

3 participants