fix(proxy): list public team model name in /v1/models#30588
Conversation
Behind general_settings.use_team_public_model_name (default False). When
enabled, /v1/models and /models surface the public team_public_model_name
for team-scoped (BYOK) models instead of the internal routing key
model_name_{team_id}_{uuid} -- consistent with /v1/model/info and
OpenAI-compatible. Off by default so the listing's model ids stay
backward-compatible for callers that scripted against the internal name;
routing by the internal name is unchanged regardless of the flag.
Presentation-layer only: access-group, auth, and routing semantics are
unchanged; non-team models are pass-through.
… tushar_byok_models_fix_update
Greptile SummaryThis PR fixes
Confidence Score: 5/5Safe to merge; the change is presentation-layer only and the routing/auth paths are untouched. The two previously flagged issues (fallback metadata being silently dropped and retrieve-by-public-name returning 404) are both addressed correctly: No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/proxy/common_utils/model_listing_utils.py | New TeamModelNameTranslator utility — correct first-wins dedup, access-scoped resolution, empty-name skip, and flag opt-out; no issues found. |
| litellm/proxy/proxy_server.py | Both listing paths use (response_id, lookup_id) pairs correctly, and retrieve resolves public→internal before access check; build_internal_to_public_map is traversed twice per retrieve call. |
| litellm/proxy/utils.py | create_model_info_response now returns a typed ModelInfoResponse and the metadata path is cleaner; behaviour is preserved. |
| litellm/types/proxy/model_listing.py | New typed ModelInfoResponse / ModelInfoMetadata TypedDicts — correct shape, metadata correctly marked NotRequired. |
| tests/test_litellm/proxy/proxy_server/test_team_model_name_translation.py | Comprehensive new tests cover access-group translation, legacy opt-out, sibling de-dup, cross-team boundary isolation, retrieve-by-public-name, retrieve echoing public id, fallback metadata via internal key, and 404 on inaccessible name. |
| tests/llm_translation/base_llm_unit_tests.py | Image URL replaced with a jsdelivr-hosted sha-pinned asset to avoid gstatic's robots.txt blocking server-side fetchers — legitimate test reliability fix, coverage unchanged. |
| ui/litellm-dashboard/src/lib/http/schema.d.ts | Generated schema updated to reflect new team_id and healthy_only query params on both retrieve operations; no functional issues. |
Reviews (12): Last reviewed commit: "test: avoid gstatic image url in image_u..." | Re-trigger Greptile
|
@greptile review |
1 similar comment
|
@greptile review |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
The listing endpoints advertise team_public_model_name, but the retrieve endpoint validated and looked up by the raw id, so a public name 404'd. Resolve the public name back to the internal routing key (scoped to the caller's accessible models so colliding names never cross teams), look up by it, and echo the public name back as the response id.
b2fc6fc to
0bb8166
Compare
…meTranslator Move the team-scoped (BYOK) listing/retrieve name translation out of proxy_server.py into a dedicated common_utils module. Static methods with general_settings injected so the logic is unit-testable without globals and proxy_server.py stays thin.
|
@greptile review |
… lookup Add listing_entries returning (public response id, internal lookup id) so include_metadata=true resolves fallbacks against the routing key the router indexes by, instead of the translated public name (which never matches).
|
@greptile review |
listing_entries built a router-wide public-name -> internal-key reverse map and used it to pick each entry's metadata_lookup_id. When two teams published the same team_public_model_name, that reverse map collapsed to whichever internal routing key appeared last in the router model list, so a caller scoped to teamX could request /v1/models?include_metadata=true and receive teamY's fallback metadata under the shared public name. The lookup id is now drawn from model_names (the caller's accessible set) directly, the same access scoping resolve_public_name already applies on the retrieve path, which also removes the global reverse map entirely. Adds a unit regression on listing_entries and an end-to-end model_list test proving the shared public name surfaces the caller's own team fallbacks, not the other team's.
create_model_info_response returned dict[str, object], which is Any-free but coarse: callers could not see that "id" is a str or that "metadata" is the fallbacks shape, and "id"/"metadata" were typed as object. Introduce a ModelInfoResponse TypedDict (with metadata as a NotRequired nested TypedDict) and build the response in one shot instead of seeding a dict and mutating it, so the OpenAI-compatible model object is precisely typed end to end while the callers' model_info["id"] = ... assignments keep checking against str.
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
|
Generated by Claude Code |
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Retrieve resolve skips list filters
- model_info now computes the same blocked/unhealthy hidden_names set as model_list (gated by a new healthy_only query param) and filters all_models before resolve_public_name, so retrieve cannot land on a deployment the listing hid.
- ✅ Fixed: Retrieve ignores list team_id filter
- model_info now accepts a team_id query param and forwards it to get_available_models_for_user, so retrieve sees the same team-scoped accessible list as the corresponding /v1/models?team_id=... call.
You can send follow-ups to the cloud agent here.
GET /v1/models/{model_id} loaded models without the listing's team_id
scoping and without the blocked/unhealthy filter, so a public id shown
by /v1/models could resolve to a different internal deployment on
retrieve. The retrieve endpoint now accepts the same team_id and
healthy_only params and applies the same hidden_names filter before
resolving the public name, keeping list and retrieve in agreement on
which accessible deployment wins under first-occurrence dedup.
|
Generated by Claude Code |
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Empty public name maps listing
- Added truthy checks on team_id and team_public alongside the isinstance(str) checks in _internal_public_pair so empty strings no longer map an internal routing key to an empty public id, matching the falsy-skip behavior of _translate_model_name_for_response.
You can send follow-ups to the cloud agent here.
Align _internal_public_pair with the falsy check used by _translate_model_name_for_response so a misconfigured row with an empty team_public_model_name no longer emits a listing entry whose id is the empty string. The internal routing key now passes through unchanged on /v1/models, matching what /v1/model/info already does.
…uiltin set
The mirrored team_id/healthy_only query params on GET /v1/models/{id} and
/models/{id} changed the OpenAPI spec, so regenerate the dashboard schema
types to match. Also annotate the new unhealthy_names with builtin set[str]
so it stays under the UP006 ruff-strict ceiling.
|
Generated by Claude Code |
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Retrieve echoes path not list id
- Map the resolved internal routing key back through the same TeamModelNameTranslator internal_to_public table the listing uses, so retrieve returns the public team name for the deployment regardless of whether the path was the public name or the internal key, with a regression test covering the internal-key path.
You can send follow-ups to the cloud agent here.
…path
GET /v1/models/{id} accepts both the public team name and the internal
routing key, but the response always echoed the path back as the response
id. Retrieving by the internal key under team-public-name translation
returned the internal string while /v1/models advertised the public name
for the same deployment, breaking OpenAI-compatible listing/retrieve
consistency.
Map the resolved internal key back through the same internal-to-public
table the listing uses; falls back to the path for globals and when the
flag is disabled.
|
Generated by Claude Code |
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ 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 69ed748. Configure here.
Anthropic (and other server-side image fetchers) now get a 400 because www.gstatic.com's robots.txt disallows crawling, breaking the detail=low/high cases of the shared base test_image_url across providers. Reuse the sha-pinned in-repo logo served via jsdelivr, which the detail=None cases already fetch successfully.
|
Generated by Claude Code |
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ 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 11b70ac. Configure here.
60f4c01
into
litellm_internal_staging
* fix(proxy): optionally surface public team model name in /v1/models
Behind general_settings.use_team_public_model_name (default False). When
enabled, /v1/models and /models surface the public team_public_model_name
for team-scoped (BYOK) models instead of the internal routing key
model_name_{team_id}_{uuid} -- consistent with /v1/model/info and
OpenAI-compatible. Off by default so the listing's model ids stay
backward-compatible for callers that scripted against the internal name;
routing by the internal name is unchanged regardless of the flag.
Presentation-layer only: access-group, auth, and routing semantics are
unchanged; non-team models are pass-through.
* fix(proxy): default team model listings to public names
* test(proxy): cover team model listing metadata
* test(proxy): cover empty team listing deployments
* refactor(proxy): simplify team model listing translation
* fix(proxy): resolve public team model name on GET /v1/models/{id}
The listing endpoints advertise team_public_model_name, but the retrieve
endpoint validated and looked up by the raw id, so a public name 404'd.
Resolve the public name back to the internal routing key (scoped to the
caller's accessible models so colliding names never cross teams), look up
by it, and echo the public name back as the response id.
* test(proxy): cover public-name resolution on model retrieve
* refactor(proxy): extract team model-name translation into TeamModelNameTranslator
Move the team-scoped (BYOK) listing/retrieve name translation out of
proxy_server.py into a dedicated common_utils module. Static methods with
general_settings injected so the logic is unit-testable without globals and
proxy_server.py stays thin.
* refactor(proxy): use TeamModelNameTranslator in model_list and model_info
* test(proxy): target TeamModelNameTranslator for model-name translation
* fix(proxy): type create_model_info_response return as dict[str, object]
* fix(proxy): keep internal routing key for team model listing metadata lookup
Add listing_entries returning (public response id, internal lookup id) so
include_metadata=true resolves fallbacks against the routing key the router
indexes by, instead of the translated public name (which never matches).
* fix(proxy): build /v1/models metadata from internal key, show public id
* test(proxy): cover team listing fallback metadata via internal key
* fix(proxy): use builtin dict generics in create_model_info_response (UP006)
---------
Co-authored-by: Tushar More <tusharmore8408@gmail.com>
Co-authored-by: Ishaan Jaffer <ishaanjaffer0324@gmail.com>
(cherry picked from commit 60f4c01)
….2) (#327) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.89.1` → `v1.89.2` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.89.2`](https://github.com/BerriAI/litellm/releases/tag/v1.89.2) [Compare Source](BerriAI/litellm@v1.89.2...v1.89.2) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.89.2 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.2/cosign.pub \ ghcr.io/berriai/litellm:v1.89.2 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(ui): rebuild ui by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​30703](BerriAI/litellm#30703) - chore(release): backport [#​30380](BerriAI/litellm#30380), [#​30503](BerriAI/litellm#30503), [#​30558](BerriAI/litellm#30558), [#​30130](BerriAI/litellm#30130), [#​30588](BerriAI/litellm#30588), [#​30495](BerriAI/litellm#30495), [#​30690](BerriAI/litellm#30690) to stable/1.89.x and cut 1.89.2 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​30681](BerriAI/litellm#30681) **Full Changelog**: <BerriAI/litellm@v1.89.1...v1.89.2> ### [`v1.89.2`](https://github.com/BerriAI/litellm/releases/tag/v1.89.2) [Compare Source](BerriAI/litellm@v1.89.1...v1.89.2) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.89.2 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.2/cosign.pub \ ghcr.io/berriai/litellm:v1.89.2 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(ui): rebuild ui by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​30703](BerriAI/litellm#30703) - chore(release): backport [#​30380](BerriAI/litellm#30380), [#​30503](BerriAI/litellm#30503), [#​30558](BerriAI/litellm#30558), [#​30130](BerriAI/litellm#30130), [#​30588](BerriAI/litellm#30588), [#​30495](BerriAI/litellm#30495), [#​30690](BerriAI/litellm#30690) to stable/1.89.x and cut 1.89.2 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​30681](BerriAI/litellm#30681) **Full Changelog**: <BerriAI/litellm@v1.89.1...v1.89.2> </details> --- ### Configuration 📅 **Schedule**: (in timezone America/New_York) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMjQuMCIsInVwZGF0ZWRJblZlciI6IjQzLjIyNC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.greyrock.io/greyrock-labs/home-ops/pulls/327
…to v1.89.2 (#206) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [https://github.com/BerriAI/litellm.git](https://github.com/BerriAI/litellm) | patch | `v1.89.1` → `v1.89.2` | --- ### Release Notes <details> <summary>BerriAI/litellm (https://github.com/BerriAI/litellm.git)</summary> ### [`v1.89.2`](https://github.com/BerriAI/litellm/releases/tag/v1.89.2) [Compare Source](BerriAI/litellm@v1.89.1...v1.89.2) #### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.89.2 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.2/cosign.pub \ ghcr.io/berriai/litellm:v1.89.2 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** #### What's Changed - chore(ui): rebuild ui by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​30703](BerriAI/litellm#30703) - chore(release): backport [#​30380](BerriAI/litellm#30380), [#​30503](BerriAI/litellm#30503), [#​30558](BerriAI/litellm#30558), [#​30130](BerriAI/litellm#30130), [#​30588](BerriAI/litellm#30588), [#​30495](BerriAI/litellm#30495), [#​30690](BerriAI/litellm#30690) to stable/1.89.x and cut 1.89.2 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​30681](BerriAI/litellm#30681) **Full Changelog**: <BerriAI/litellm@v1.89.1...v1.89.2> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMjAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjIyMC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=--> Co-authored-by: Renovate Bot <renovate@bhamm-lab.com> Reviewed-on: https://codeberg.org/blake-hamm/bhamm-lab/pulls/206
….2) (#143) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.89.1` → `v1.89.2` | --- ### Release Notes <details> <summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary> ### [`v1.89.2`](https://github.com/BerriAI/litellm/releases/tag/v1.89.2) [Compare Source](BerriAI/litellm@v1.89.2...v1.89.2) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.89.2 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.2/cosign.pub \ ghcr.io/berriai/litellm:v1.89.2 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(ui): rebuild ui by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​30703](BerriAI/litellm#30703) - chore(release): backport [#​30380](BerriAI/litellm#30380), [#​30503](BerriAI/litellm#30503), [#​30558](BerriAI/litellm#30558), [#​30130](BerriAI/litellm#30130), [#​30588](BerriAI/litellm#30588), [#​30495](BerriAI/litellm#30495), [#​30690](BerriAI/litellm#30690) to stable/1.89.x and cut 1.89.2 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​30681](BerriAI/litellm#30681) **Full Changelog**: <BerriAI/litellm@v1.89.1...v1.89.2> ### [`v1.89.2`](https://github.com/BerriAI/litellm/releases/tag/v1.89.2) [Compare Source](BerriAI/litellm@v1.89.1...v1.89.2) ##### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.89.2 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.2/cosign.pub \ ghcr.io/berriai/litellm:v1.89.2 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** ##### What's Changed - chore(ui): rebuild ui by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​30703](BerriAI/litellm#30703) - chore(release): backport [#​30380](BerriAI/litellm#30380), [#​30503](BerriAI/litellm#30503), [#​30558](BerriAI/litellm#30558), [#​30130](BerriAI/litellm#30130), [#​30588](BerriAI/litellm#30588), [#​30495](BerriAI/litellm#30495), [#​30690](BerriAI/litellm#30690) to stable/1.89.x and cut 1.89.2 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​30681](BerriAI/litellm#30681) **Full Changelog**: <BerriAI/litellm@v1.89.1...v1.89.2> </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/London) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMzIuMCIsInVwZGF0ZWRJblZlciI6IjQzLjIzMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://forgejo.hayden.moe/hayden/phoebe/pulls/143
* fix(proxy): optionally surface public team model name in /v1/models
Behind general_settings.use_team_public_model_name (default False). When
enabled, /v1/models and /models surface the public team_public_model_name
for team-scoped (BYOK) models instead of the internal routing key
model_name_{team_id}_{uuid} -- consistent with /v1/model/info and
OpenAI-compatible. Off by default so the listing's model ids stay
backward-compatible for callers that scripted against the internal name;
routing by the internal name is unchanged regardless of the flag.
Presentation-layer only: access-group, auth, and routing semantics are
unchanged; non-team models are pass-through.
* fix(proxy): default team model listings to public names
* test(proxy): cover team model listing metadata
* test(proxy): cover empty team listing deployments
* refactor(proxy): simplify team model listing translation
* fix(proxy): resolve public team model name on GET /v1/models/{id}
The listing endpoints advertise team_public_model_name, but the retrieve
endpoint validated and looked up by the raw id, so a public name 404'd.
Resolve the public name back to the internal routing key (scoped to the
caller's accessible models so colliding names never cross teams), look up
by it, and echo the public name back as the response id.
* test(proxy): cover public-name resolution on model retrieve
* refactor(proxy): extract team model-name translation into TeamModelNameTranslator
Move the team-scoped (BYOK) listing/retrieve name translation out of
proxy_server.py into a dedicated common_utils module. Static methods with
general_settings injected so the logic is unit-testable without globals and
proxy_server.py stays thin.
* refactor(proxy): use TeamModelNameTranslator in model_list and model_info
* test(proxy): target TeamModelNameTranslator for model-name translation
* fix(proxy): type create_model_info_response return as dict[str, object]
* fix(proxy): keep internal routing key for team model listing metadata lookup
Add listing_entries returning (public response id, internal lookup id) so
include_metadata=true resolves fallbacks against the routing key the router
indexes by, instead of the translated public name (which never matches).
* fix(proxy): build /v1/models metadata from internal key, show public id
* test(proxy): cover team listing fallback metadata via internal key
* fix(proxy): use builtin dict generics in create_model_info_response (UP006)
---------
Co-authored-by: Tushar More <tusharmore8408@gmail.com>
Co-authored-by: Ishaan Jaffer <ishaanjaffer0324@gmail.com>
(cherry picked from commit 60f4c01)
What changed
/v1/modelsand/modelsbuild their list from access-group / key-model expansion, which surfaces the internal routing keymodel_name_{team_id}_{uuid}for team-scoped (BYOK) models rather than an OpenAI-compatible model id./v1/model/infoalready translates these to the publicteam_public_model_name; this makes the listing endpoints consistent.By default the listing now returns the human-friendly
team_public_model_name. Operators that scripted against the legacy internal keys can opt out withgeneral_settings.use_team_public_model_name: false; the routing key still works for API calls regardless of the flag (routing is unchanged). This is a presentation-layer change only; access-group and auth semantics are untouched.GET /v1/models/{model_id}now accepts the public name too, resolving it back to the internal routing key for the deployment lookup so a name advertised by the listing does not 404 on retrieve. Wheninclude_metadata=true, the fallback lookup runs against the internal routing key (what the router indexes fallbacks by) while the response id stays the public name, so configured fallbacks for team models are not silently dropped.To keep listing and retrieve fully consistent, the retrieve path mirrors the listing's caller context: it accepts the same
team_idandhealthy_onlyquery params and applies the same blocked/unhealthy hiding, so a public id advertised by/v1/modelsresolves to the same deployment on retrieve. Sibling deployments that share a public name collapse to a single entry by first occurrence, and a team row with an emptyteam_public_model_nameis left untranslated (the internal key is shown) rather than emitting a blank id. Retrieve also echoes the public id/v1/modelsadvertises even when the caller addresses the deployment by its internal routing key, so both endpoints report the same id.Relevant issues
Fixes #30496
Pre-Submission checklist
make test-unitType
🐛 Bug Fix
Changes
The translation logic lives in
litellm/proxy/common_utils/model_listing_utils.pyasTeamModelNameTranslator(static methods,general_settingsinjected) soproxy_server.pystays thin.model_listbuilds each entry fromlisting_entries(...), which returns a(public response id, internal lookup id)pair: the public name is what the client sees, the internal key drives the metadata/fallback lookup.model_inforesolves a requested public name to the internal key, scoped to the caller's accessible models so colliding public names across teams never cross an access boundary, then echoes the public id back in the response.Tests cover access-group translation, the legacy opt-out flag, sibling de-dupe, no-router passthrough, retrieve-by-public-name (and the access-scoped 404), retrieve-by-internal-key echoing the public id, the empty public-name skip, and fallback metadata resolving via the internal routing key.
Screenshots / Proof of Fix
Local DB-backed proxy on
localhost:4000with two team-scoped BYOK deployments (team-gpt-4o-mini-a,team-gpt-4o-mini-b) tagged with access groupgrp-a, plus a virtual key scoped togrp-a. That key lists models through access-group expansion, which is exactly where the internal routing key used to leak.After (this PR, default behavior):
curl -s http://localhost:4000/v1/models -H "Authorization: Bearer $KEY"{ "data": [ {"id": "team-gpt-4o-mini-a", "object": "model", "created": 1677610602, "owned_by": "openai"}, {"id": "team-gpt-4o-mini-b", "object": "model", "created": 1677610602, "owned_by": "openai"} ], "object": "list" }Before, reproduced on the same proxy by setting the legacy opt-out
general_settings.use_team_public_model_name: false, the same key sees the internal routing keys leak:{ "data": [ {"id": "model_name_c52aee85-07c7-492a-8337-01f1cd85c376_EAD5537F-3A6E-41F1-BE16-DB66CCD5F76A", "object": "model", "created": 1677610602, "owned_by": "openai"}, {"id": "model_name_c52aee85-07c7-492a-8337-01f1cd85c376_BF3363CA-1DDB-4F3A-9A14-4D2EEEFCABA5", "object": "model", "created": 1677610602, "owned_by": "openai"} ], "object": "list" }Note
Medium Risk
Changes model id presentation and retrieve resolution across auth-scoped listings; mistakes could leak wrong-team metadata or break clients expecting internal keys, though routing and access checks are preserved and heavily tested.
Overview
/v1/modelsand/modelsnow show team BYOK deployments asteam_public_model_nameinstead of internalmodel_name_{team_id}_{uuid}keys (aligned with/v1/model/info). Routing is unchanged; operators can restore internal ids withgeneral_settings.use_team_public_model_name: false.A new
TeamModelNameTranslatorbuilds listing entries as (public response id, internal lookup id) so clients see friendly names while metadata/fallback lookups still use router keys. Shared public names dedupe with first-wins ordering, stay scoped to the caller’s accessible models, and skip empty public names.GET /v1/models/{id}accepts public names, addsteam_idandhealthy_onlyto match list filtering, resolves to the same internal deployment as the list, and always returns the public id when translation is on (including when queried by internal key).create_model_info_responseis typed viaModelInfoResponse; OpenAPI schema and broad regression tests cover translation, metadata fallbacks, and access boundaries.Reviewed by Cursor Bugbot for commit 11b70ac. Bugbot is set up for automated code reviews on this repo. Configure here.