Skip to content

[Fix] Team-based model name corruption on PATCH - #27376

Closed
yuneng-berri wants to merge 2 commits into
litellm_internal_stagingfrom
litellm_/laughing-newton-cd0899
Closed

[Fix] Team-based model name corruption on PATCH#27376
yuneng-berri wants to merge 2 commits into
litellm_internal_stagingfrom
litellm_/laughing-newton-cd0899

Conversation

@yuneng-berri

Copy link
Copy Markdown
Collaborator

Relevant issues

Summary

Failure Path (Before Fix)

For team-scoped ("Team-BYOK") models, the backend stores two names:

  • model_name (DB column): an internal routing key model_name_{team_id}_{uuid}, used by the in-memory router's primary model_name → deployment index. Two teams with the same public name need distinct routing keys here.
  • model_info.team_public_model_name: the user-facing public name (e.g. gpt-4o).

The internal name was leaking into /v1/model/info and /v2/model/info API responses. The UI bound the model edit form to that field, so any non-rename edit (e.g. changing TPM) PATCHed the model with the internal name as model_name. The PATCH endpoint then treated it as a new public name, overwrote team_public_model_name, and rewrote the team's models[] list with the mangled string — breaking team-based routing for that model.

Fix

  1. Add _translate_model_name_for_response in proxy_server.py. For team-scoped rows, swap model_name to the public name held in model_info.team_public_model_name before returning. Apply at _get_proxy_model_info (covers /v1/model/info, /model/info) and in /v2/model/info before pagination. The DB column and router index keep the internal name as the routing key — the swap is presentation-layer only.

  2. Harden _get_public_model_name in model_management_endpoints.py. When patch_data.model_name matches the internal mangled shape model_name_{team_id}_{uuid} or equals the existing DB column, treat it as "no rename intent" and fall through to the existing team_public_model_name. This protects against any client (UI, scripts, future code) that round-trips the leaked internal name.

  3. Standardize AgentBuilderView selection on model_info.id instead of model_name. Once response translation lands, two team-scoped agents can share a public name; the lookup needs a stable id to avoid collisions.

Testing

Verified end-to-end against a local proxy with a regression harness exercising three PATCH scenarios on fixtures including a deliberate cross-team public-name collision:

  • TPM-only edit on a team model — team_public_model_name and the team's models[] list are unchanged after the PATCH; only litellm_params.tpm updates.
  • Genuine rename on a team model — still works; team's models[] list updates correctly.
  • Defensive case: client PATCHes with a fabricated model_name_{team_id}_{uuid} payload — treated as a no-op for the public name; no corruption.

Across all scenarios, the DB model_name column for team rows is unchanged (router invariant preserved). Two fresh captures on the fixed backend produce zero diffs against each other (determinism).

Type

🐛 Bug Fix

Screenshots

@yuneng-berri
yuneng-berri requested a review from a team May 7, 2026 06:52
For team-scoped ("Team-BYOK") models, the backend stores an internal
routing key `model_name_{team_id}_{uuid}` in the `model_name` column and
the user-facing name in `model_info.team_public_model_name`. The
internal name was leaking into `/v1/model/info` and `/v2/model/info`
responses. The UI bound the model edit form to that field, so any
non-rename edit (e.g. changing TPM) would PATCH the model with the
internal name as `model_name`. The PATCH endpoint then treated it as
a new public name, overwriting `team_public_model_name` and rewriting
the team's `models[]` list with the mangled string.

Translate `model_name` to the public name in API responses for
team-scoped rows; the DB column and router index continue to use the
internal name as the routing key. Harden `_get_public_model_name` to
detect a no-op or internal-shape `model_name` in the patch and fall
through to the existing `team_public_model_name` instead of treating
it as a rename. Standardize `AgentBuilderView` selection on
`model_info.id` so cross-team agents with the same public name don't
collide.
@yuneng-berri
yuneng-berri force-pushed the litellm_/laughing-newton-cd0899 branch from d77f5c6 to c96d471 Compare May 7, 2026 06:53
@yuneng-berri

Copy link
Copy Markdown
Collaborator Author

@greptile

@codecov

codecov Bot commented May 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a data-corruption bug where the internal routing key (model_name_{team_id}_{uuid}) for team-BYOK models was leaking into API responses; the UI would then PATCH with that key as the new public name, overwriting team_public_model_name and breaking team routing.

  • proxy_server.py: Adds _translate_model_name_for_response which swaps the internal routing key for the public name in /v1/model/info and /v2/model/info responses; the DB column and router index are untouched.
  • model_management_endpoints.py: Hardens _get_public_model_name to treat a round-tripped internal name or an exact no-op match as "no rename intent", falling through to the existing team_public_model_name.
  • AgentBuilderView.tsx: Switches agent selection state to use the stable model_info.id (falling back to model_name for config-file agents), fixing post-create/update/delete selection and resolving cross-team public-name collision issues in the sidebar.

Confidence Score: 4/5

Safe to merge with minor ordering and name-restriction edge cases worth addressing in a follow-up.

The core corruption fix is sound and presentation-only — DB and router state are never touched. Two minor edge cases exist: BYOK rows sort by their internal routing key when sortBy=model_name (sort runs before translation), and the is_internal_shape prefix guard is slightly over-broad, blocking human-chosen names that happen to share the routing-key prefix.

The sort-then-translate ordering in proxy_server.py and the over-broad internal-shape check in model_management_endpoints.py are the two spots worth a follow-up look.

Important Files Changed

Filename Overview
litellm/proxy/management_endpoints/model_management_endpoints.py Hardens _get_public_model_name to detect and suppress round-tripped internal routing-key names; logic is correct for the primary BYOK fix path.
litellm/proxy/proxy_server.py Adds _translate_model_name_for_response and applies it in _get_proxy_model_info and model_info_v2; the translation fires after sorting, meaning sort-by-model_name returns BYOK rows in routing-key order, not public-name order.
ui/litellm-dashboard/src/components/playground/chat_ui/AgentBuilderView.tsx Switches agent selection key to model_info.id (with model_name fallback), fixes post-create/update/delete selection to use stable id, and returns list from loadAgents to avoid stale-closure issues.

Reviews (2): Last reviewed commit: "[Fix] Address review feedback on AgentBu..." | Re-trigger Greptile

Comment thread ui/litellm-dashboard/src/components/playground/chat_ui/AgentBuilderView.tsx Outdated
Comment thread ui/litellm-dashboard/src/components/playground/chat_ui/AgentBuilderView.tsx Outdated
- Apply Black formatting to _get_public_model_name (lint fix).
- AgentBuilder: introduce getAgentSelectionKey(agent) returning model_info.id
  with a fallback to model_name. Use it for the sidebar onClick, find/some
  lookups, and the active-state comparison so config-file-defined agents
  (which have no DB id) remain selectable.
- AgentBuilder handleSaveAgent: use the create response's model_id to
  select the just-created agent rather than a model_name lookup, since two
  team-scoped agents can legitimately share the same public name.
@yuneng-berri

Copy link
Copy Markdown
Collaborator Author

@greptile

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@github-actions github-actions Bot added the stale label Aug 6, 2026
@github-actions github-actions Bot closed this Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant