Skip to content

[Feature] UI - Teams: Allow Editing Router Settings After Team Creation#25398

Merged
yuneng-berri merged 11 commits intomainfrom
litellm_team_settings_router
Apr 14, 2026
Merged

[Feature] UI - Teams: Allow Editing Router Settings After Team Creation#25398
yuneng-berri merged 11 commits intomainfrom
litellm_team_settings_router

Conversation

@yuneng-berri
Copy link
Copy Markdown
Collaborator

Relevant issues

Summary

Problem

Users can set router settings when creating a team, but cannot edit them afterward in the UI. The backend endpoints fully support router_settings, so the gap was purely in the frontend.

Fix

Reuse the existing RouterSettingsAccordion component (already used in the create flow) in the team Settings tab. The component is rendered inline with its Loadbalancing/Fallbacks tabs visible directly — no extra accordion wrapper.

Also fixes two pre-existing bugs in RouterSettingsAccordion:

  • Numeric fields (retries, timeout, etc.) were not captured on save because the component uses uncontrolled DOM inputs and the onChange only fired on strategy/fallback changes. Fixed by reading values via ref.getValue() at save time.
  • Clearing a numeric field preserved the old value instead of setting it to null. Fixed by treating empty DOM inputs as null.

Changes

  • Added router_settings to TeamData interface and wired RouterSettingsAccordion into the team edit form with a ref-based approach (pass initial value once, read at save time)
  • Added a read-only summary showing routing strategy, numeric settings, and fallback count
  • Fixed RouterSettingsAccordion to return null for empty inputs instead of stale state values
  • Added getRouterSettingsCall mock to TeamInfo tests

Testing

  • Verified read-only view displays router settings (strategy badge, retries, cooldown, etc.)
  • Verified edit form pre-populates the RouterSettingsAccordion with existing values
  • Verified saving strategy changes persists to backend
  • Verified cancel resets to original values
  • Verified empty state shows "No router settings configured"
  • All 29 TeamInfo tests pass
  • All 87 router settings tests pass
  • UI build succeeds

Type

🆕 New Feature
🐛 Bug Fix
✅ Test

Add RouterSettingsAccordion to the team edit form and a read-only
display of router settings (routing strategy, retries, fallbacks,
cooldown, timeout, tag filtering) in the Settings tab.
The RouterSettingsAccordion uses uncontrolled DOM inputs for numeric fields.
The onChange effect only fires when formValue or fallbacks change, not when
the user types into uncontrolled inputs. Using the ref's getValue() method
at save time ensures we read fresh values from the DOM.
… old values

When a user clears a numeric field in the RouterSettingsAccordion, the empty
input should result in null, not fall through to the previous value from state.
The uncontrolled TabGroup reset to the first tab whenever the component
re-rendered due to onChange state updates. Using controlled index/onIndexChange
keeps the user on the current tab during fallback selection.
…tab reset"

This reverts commit 9ff4bf66b03912f2c1ab893d2bdb31fba0ea9488.
When a fallback model was selected, the onChange effect fired, parent
re-rendered with new value prop, and the useEffect re-initialized the
groups with new IDs from fallbacksToGroups. This caused the active
fallback group tab to reset to Group 1. Now internal updates always
skip re-initialization regardless of value key changes.
…hange loop

Remove the onChange/value feedback loop between TeamInfo and
RouterSettingsAccordion. The parent passes initial value once and reads
current values at save time via ref.getValue(). This eliminates the
circular data flow that caused stale state and fallback tab resets.
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 9, 2026 5:25am

Request Review

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq bot commented Apr 9, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing litellm_team_settings_router (e8c51ec) with main (97f722f)

Open in CodSpeed

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 9, 2026

Greptile Summary

This PR adds the ability to edit router_settings on a team after creation by wiring RouterSettingsAccordion into the team Settings tab, exposes a read-only summary of active settings in view mode, and fixes two pre-existing bugs in RouterSettingsAccordion (numeric fields not captured on save, and clearing a field preserving stale state). The isMeaningfulValue + hadExistingSettings logic correctly gates the update payload, addressing both the "always-sends router_settings" and "clearing-is-silently-dropped" concerns raised in previous threads.

Confidence Score: 5/5

Safe to merge — previous P1 concerns are addressed by the isMeaningfulValue + hadExistingSettings gate; the one remaining finding is a minor future-proofing suggestion.

All previously raised blocking concerns have been resolved. The only remaining finding is P2: the valueKey stability issue in RouterSettingsAccordion, which is harmless in the current implementation due to unmount-on-save.

RouterSettingsAccordion.tsx — the valueKey stability gap is worth tracking if the component is reused in a mounted-across-update context.

Vulnerabilities

No security concerns identified. The feature is purely a UI change wiring existing backend endpoints; accessToken is passed from the authenticated session and not modified. No localStorage usage, no new network calls beyond the existing teamUpdateCall and getRouterSettingsCall.

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.tsx Bug fix: empty DOM inputs now return null instead of falling through to stale state; minor: valueKey stability only tracks 3 of the ~8 router settings fields.
ui/litellm-dashboard/src/components/team/TeamInfo.tsx Adds RouterSettingsAccordion to team edit form and read-only router settings summary; save logic correctly gates updates via isMeaningfulValue + hadExistingSettings.
ui/litellm-dashboard/src/components/team/TeamInfo.test.tsx Adds getRouterSettingsCall mock to prevent real network calls in tests; mock correctly returns { fields: [] } to satisfy the component's data contract.

Sequence Diagram

sequenceDiagram
    participant User
    participant TeamInfo as TeamInfo (edit form)
    participant RouterAccordion as RouterSettingsAccordion
    participant DOM as DOM inputs
    participant Backend as teamUpdateCall

    User->>TeamInfo: Click Edit
    TeamInfo->>RouterAccordion: mount(ref, value=info.router_settings)
    RouterAccordion->>RouterAccordion: initialize state from value prop
    RouterAccordion->>DOM: render inputs (Loadbalancing tab)

    User->>TeamInfo: Click Save
    TeamInfo->>RouterAccordion: routerSettingsRef.current.getValue()
    RouterAccordion->>DOM: querySelector each input[name]
    DOM-->>RouterAccordion: current DOM values (or null if cleared)
    RouterAccordion-->>TeamInfo: { router_settings: { ... } }

    TeamInfo->>TeamInfo: isMeaningfulValue check (hasNewValues)
    TeamInfo->>TeamInfo: hadExistingSettings check (info.router_settings)
    alt hasNewValues OR hadExistingSettings
        TeamInfo->>Backend: teamUpdateCall(accessToken, { router_settings })
    else neither
        TeamInfo->>Backend: teamUpdateCall(accessToken, {})
    end
    Backend-->>TeamInfo: success
    TeamInfo->>RouterAccordion: unmount (setIsEditing false)
Loading

Reviews (3): Last reviewed commit: "fix: address PR review - allow clearing ..." | Re-trigger Greptile

Comment on lines +596 to +603
if (currentRouterSettings?.router_settings) {
const hasValues = Object.values(currentRouterSettings.router_settings).some(
(value) => value !== null && value !== undefined && value !== "",
);
if (hasValues) {
updateData.router_settings = currentRouterSettings.router_settings;
}
}
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 hasValues guard always passes, sending router_settings on every save

buildRouterSettings() always includes enable_tag_filtering: formValue.enableTagFiltering, which is false by default. Since false !== null && false !== undefined && false !== "" evaluates to true, hasValues is always truthy — even when the user never touched Router Settings. Every team save will include router_settings: { routing_strategy: null, allowed_fails: null, ..., enable_tag_filtering: false } in the update payload, which can silently overwrite existing backend state (e.g. a team with enable_tag_filtering: true set via the API would have it flipped to false on the next unrelated settings save).

Suggested change
if (currentRouterSettings?.router_settings) {
const hasValues = Object.values(currentRouterSettings.router_settings).some(
(value) => value !== null && value !== undefined && value !== "",
);
if (hasValues) {
updateData.router_settings = currentRouterSettings.router_settings;
}
}
const currentRouterSettings = routerSettingsRef.current?.getValue();
if (currentRouterSettings?.router_settings) {
const hasValues = Object.values(currentRouterSettings.router_settings).some(
(value) => value !== null && value !== undefined && value !== "" && value !== false,
);
if (hasValues) {
updateData.router_settings = currentRouterSettings.router_settings;
}
}

ref={routerSettingsRef}
accessToken={accessToken || ""}
value={info.router_settings ? { router_settings: info.router_settings } : undefined}
modelData={userModels.length > 0 ? { data: userModels.map((model) => ({ model_name: model })) } : undefined}
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 modelData prop passed but unused by the component

RouterSettingsAccordion declares modelData?: any in its props interface and destructures it, but never uses it — the component calls fetchAvailableModels(accessToken) internally to populate available models. Passing the prop here creates a misleading impression that it influences the fallback model list. The prop can be safely omitted.

…r settings

enable_tag_filtering: false (the default) passed the hasValues guard,
causing every team save to send router_settings even for teams that
never had them configured. This could silently overwrite backend-set
values. Now exclude false and empty arrays from the check.
Comment on lines +597 to +610
const currentRouterSettings = routerSettingsRef.current?.getValue();
if (currentRouterSettings?.router_settings) {
const hasValues = Object.values(currentRouterSettings.router_settings).some(
(value) =>
value !== null &&
value !== undefined &&
value !== "" &&
value !== false &&
!(Array.isArray(value) && value.length === 0),
);
if (hasValues) {
updateData.router_settings = currentRouterSettings.router_settings;
}
}
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 hasValues guard silently drops clears of all router settings

Because the guard skips false and null values, a user who clears every router setting (e.g., changes routing_strategy from "latency-based-routing" to no selection, with enable_tag_filtering unchecked) gets hasValues = false — and router_settings is never sent in the update payload. The backend retains the old values, with no error shown to the user.

A concrete failure path: team has { routing_strategy: "latency-based-routing" }. User opens edit, clears the strategy dropdown, saves. buildRouterSettings() returns all-null/false, hasValues = false, update is skipped silently.

A simple fix is to always send router_settings when the team already had them:

Suggested change
const currentRouterSettings = routerSettingsRef.current?.getValue();
if (currentRouterSettings?.router_settings) {
const hasValues = Object.values(currentRouterSettings.router_settings).some(
(value) =>
value !== null &&
value !== undefined &&
value !== "" &&
value !== false &&
!(Array.isArray(value) && value.length === 0),
);
if (hasValues) {
updateData.router_settings = currentRouterSettings.router_settings;
}
}
const currentRouterSettings = routerSettingsRef.current?.getValue();
if (currentRouterSettings?.router_settings) {
const existingRouterSettings = info.router_settings;
const hasExistingSettings = existingRouterSettings && Object.values(existingRouterSettings).some(
(value) =>
value !== null &&
value !== undefined &&
value !== "" &&
value !== false &&
!(Array.isArray(value) && value.length === 0),
);
const hasValues = Object.values(currentRouterSettings.router_settings).some(
(value) =>
value !== null &&
value !== undefined &&
value !== "" &&
value !== false &&
!(Array.isArray(value) && value.length === 0),
);
// Send router_settings if there are new values OR if the user is clearing existing ones
if (hasValues || hasExistingSettings) {
updateData.router_settings = currentRouterSettings.router_settings;
}
}

…d modelData prop

- Send router_settings when clearing existing settings (so backend
  updates instead of silently retaining old values)
- Remove unused modelData prop from RouterSettingsAccordion (it fetches
  models internally via fetchAvailableModels)
@yuneng-berri yuneng-berri merged commit c25b4b2 into main Apr 14, 2026
103 of 107 checks passed
@yuneng-berri yuneng-berri deleted the litellm_team_settings_router branch April 14, 2026 00:27
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