Repository navigation
fix(proxy): list only routable ids for partial wildcards on /v1/models and /model/info - #43643
devin-ai-integration[bot] wants to merge 22 commits into
Conversation
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
|
[Medium impact] Refactors wildcard model matching and listing logic. Fix the missing exact-model check before merging so scoped keys keep discovering working models Findings
SummaryThe PR makes wildcard listings use callable IDs, removes duplicates, and improves health probes. The latest change also checks registered routes when a key's granted pattern is not itself registered
Reviews (15) · Last reviewed commit: "fix(proxy): skip ids a narrower route se..." · Reviewed by Greptile |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…d model ids Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ck candidates Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ck call site 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>
…l prefix Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…red proxy Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yuneng-jiang <yuneng.jiang@gmail.com> Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
f573559 to
b258196
Compare
|
@mateo-berri rebased onto main with the #46390 de-dupe folded in. The description compares both PRs live, your call on which to merge |
…ldcard, drop guessed splices A partial wildcard now lists model_name's literal plus the suffix the router would substitute into litellm_params.model, so every listed id routes to a known provider model. A partial wildcard matching no known model lists nothing, and the health check falls back to main's cheapest candidates.
|
@litereview code-review |
✅ code-review5/5 · Ready for human reviewScope: full · Full PR review. No correctness findings. I recheck the earlier many- Checks and execution details
Call routing has not changed:
Health check path: Boundary checks:
Tests:
No defects remain open.
View review · Reviewed |
…custom_llm_provider, /model/info agreement, health probe fallback, and team file credentials beside a narrower wildcard The key listing test now also checks that /model/info lists the narrowly served id exactly when the call reaches the model it names, across responses/, bare-model-with-provider and other-provider narrower routes. New cells cover a wildcard health check whose pattern models all fail while a provider model answers, and a team file listing that takes the credentials of a narrower Azure wildcard route.
…ildcard deployment
…er wildcard, and treat an aggregator's vendor-namespaced model as the same model
…ard_prefix_expansion # Conflicts: # litellm/proxy/auth/model_checks.py
|
@litereview code-review |
…p team route ids a proxy-wide route serves first
|
@litereview code-review |
1 similar comment
|
@litereview code-review |
Review already requestedcode-review is already reviewing |
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit bb2b5b1. Configure here.
| if own_regex not in self.patterns: | ||
| return None | ||
| serving_regex: Final = next((regex for regex in self.patterns if matches_from_start(regex, request)), own_regex) | ||
| return None if serving_regex == own_regex else serving_regex |
There was a problem hiding this comment.
Unregistered key grants skip shadowing
Medium Severity
shadowing_regex returns none when the listed wildcard is not a registered route, so routes_elsewhere never drops ids. A key scoped to a pattern that is only a grant, not a route, still expands through the parent wildcard and can list an id that a narrower route will serve as a different, uncallable model.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit bb2b5b1. Configure here.
… granted a wildcard that is not a route
|
@litereview code-review |
| serving_regex: Final = next((regex for regex in self.patterns if matches_from_start(regex, request)), None) | ||
| return None if serving_regex == own_regex else serving_regex |
There was a problem hiding this comment.
Working exact models disappear
Removing the registration guard can hide an exact model from a key whose granted pattern is not registered. For example, configure openai/* over openai/*, openai/gpt-4o* over anthropic/claude-sonnet-4*, and exact openai/gpt-4o-mini over itself. A key granted openai/gpt-4* now loses openai/gpt-4o-mini from both listing endpoints, although calls still use the exact deployment
This lookup checks only wildcard routes, and the other-route listing cannot restore exact names. Include exact deployments when deciding which route serves an ID
Knowledge Base Used: Routing and resilience


TLDR
Problem this solves:
cloudflare/@cf/*list no ids, and ids that an exact or narrower route also serves are listed twicegemini/gemini-2.5*lists 4 of its 10 modelsteam/*overanthropic/claude-*, a team's own routes, and bare models withcustom_llm_providerlist ids that fail and leave out ids that workHow it solves it:
/model/inforows name the serving deployment, the routed model, and its priceDecisions and the alternatives they beat
*on each side, the id is the route with its*filled by the part of the model id the deployment's*coversteam/*overanthropic/claude-*liststeam/haiku-4-5, which routes toanthropic/claude-haiku-4-5team/claude-haiku-4-5, sent asclaude-claude-haiku-4-5and answered 404custom/gemini-*overgemini/*listscustom/gemini-gemini-2.5-flash, andopenai/gpt-4*overopenai/*listsopenai/gpt-4gpt-4o*, a route lists ids only when it ends with the deployment's pattern under a plain namespacefixed/*overopenai/gpt-4o-mini, lists one id,fixed/gpt-4o-miniresponses/for every provider, every Bedrock route prefix that targets a model (likeconverse/,invoke/,nova/,claude_platform/,mantle/), and Azure'so_series/andgpt5_series/openai/*overopenai/responses/*keeps listingopenai/gpt-4o-miniagent/andagentcore/are left out, since they target agents, not cost-map models/v1/modelsskips an id only when a more specific route serves it and sends it to a different model*overgroq/*next toopenai/*does not listopenai/gpt-oss-120b, since the router sends that id toopenai/*openai/*still listsopenai/gpt-4.1-mininext to anopenai/gpt-4.1*route, since both send it to the same modelwide/gpt-4o*overopenai/responses/gpt-4o*,cust/gpt-4.1*over a baregpt-4.1*withcustom_llm_provider: openai, andaz/gpt-4o*overazure/gpt-4o*keep the wider route's idsopenrouter/openai/gpt-4.1*, counts as the same model asopenai/gpt-4.1*wide/claude-*beside the proxy-widewide/*lists nothing, sincewide/*sends those ids to OpenAI, which answers 404tw/claude-*, which no proxy-wide route matches, lists its 22 ids for a team key ontw/*wide/claude-haiku-4-5for a team key onwide/*(live risk check at ef70f84)custom_llm_provider, likegpt-4*withcustom_llm_provider: openai, lists the same ids asopenai/gpt-4*g4/*over it listsg4/o-mini, andopenai/*over a bare*lists OpenAI's 208 idsg4/id on/v1/models, and 1442 ids underopenai/*, 75 of themopenai/claude...oropenai/gemini...ids/model/infolists each id once, under the deployment that serves it, an exact deployment included*over a provider lists bare ids, likegpt-4o-minifor*overopenai/*openai/gpt-4o-mini, which that route sends asopenai/openai/gpt-4o-miniand OpenAI answers 400ollama/underollama_chatandazure/underazure_textare strippedopenai/underwandbandmeta/underreplicatefnmatchcase, keeping[and?literal, since only*is a wildcard in a route name*cannot stall the listingre.matchgroups in linear time.*, which backtracks exponentially on a pattern with many*re.matchfor the captures only, which took 44 s on/v1/modelsbesidens/********g(audit round 2)wide/gpt-4o*unhealthy after its audio and search preview models answered 404 and 400 (live risk check at 668fa29)databricks/system.ai.*that serve fineListings change on purpose, as #46384 asks. Calls work as before
User Flow
Before: a proxy admin with a
cloudflare/@cf/*route finds no Cloudflare id to callcloudflare/@cf/*route and start the proxycloudflare/@cf/*rowAfter: the same route lists every Cloudflare model once, and the listed ids work
cloudflare/@cf/meta/llama-3.2-1b-instructRelevant issues
Fixes #46384
Linear ticket
Resolves LIT-8910
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/unit/<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 moreDelays 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
Two live proxies, one per tree, 2 uvicorn workers each, real provider keys, and curl. Raw output is in this gist
Route configs
Every proxy runs with
LITELLM_LOCAL_MODEL_COST_MAP=True. The scoped-key, infix, shadow, narrower-route, cross-region, and team cases use Postgrescloudflare/@cf/*idsgemini/gemini-2.5*idsfireworks_ai/.../llama*idscustom/gemini-*idteam/*overclaude-*id*overcloudflare/@cf/*ids*overgemini-2.5*id*overopenai/*idopenai/*over bare*idsopenai/claudeandopenai/geminiidsg4/*over baregpt-4*idsteam/oai-gpt-4o-minipricefixed/*overopenai/gpt-4o-miniidscloudflare/@cf/ids/model/inforows, id an exact or narrower route servesgstars/gpt-4o-minilistingsbrk/anthropic.ids on/v1/modelsorr/*andbrk/*,brk/anthropic.ids on/v1/modelsand/model/infowide/claude-idstw/*,tw/claude-idsRows with the same result on both legs
openai/*overopenai/responses/*idsbedrock/*andbnova/*infix route idsopenai/*,gpt-4.1ids*routesaz/*team, Azure file uploadorr/gpt-4oandbrk/anthropic.idswide/claude-haiku-4-5calltw/*,tw/claude-haiku-4-5callwide/gpt-4o*andgemini/gemini-2.5*healthBoth legs report every health check healthy at this base, so unit tests, not this proof, cover probing inside the route
Before (b6ca57b)
All cases
Cloudflare partial wildcard
/v1/modelslists no Cloudflare id, and/model/infoonly the raw route rowOutput in before_static.txt
Gemini 2.5 partial wildcard
/v1/modelslists 4 audio or TTS ids, missinggemini-2.5-flashandgemini-2.5-proOutput in before_static.txt
gemini/gemini-2.5-flash-liteis not listed, though calling it worksOutput in before_static.txt
Output in before_static.txt
Fireworks partial wildcard
/v1/modelslists no Fireworks idOutput in before_static.txt
Alias that rewrites part of the model name
custom/gemini-*overgemini/*lists 12 ids, and a listed one answers 404Output in before_static.txt
team/*overanthropic/claude-*lists 22 ids, and a listed one is sent asclaude-claude-...and answers 404Output in before_alias.txt
Alias that renames only the namespace
genai/test/*overopenai/*lists 212 ids, 4 of them keeping theopenai/prefix, likegenai/test/openai/sora-2, and a listed one worksOutput in before_alias.txt
team/claude-*overanthropic/claude-*lists 22 ids, and a listed one worksOutput in before_static.txt
Route-type infix, prefix alias, and fixed target
fixed/*lists 208 ids, and/model/infogives the alias row no priceOutput in before_routes.txt
Output in before_routes.txt
nova/,claude_platform/, andmantle/routes list ids on both endpoints, a listed one works, and the health check reports the route healthyOutput in before_infix.txt
Route shadowed by a more specific one
*overgroq/*lists noopenai/gpt-ossid, and callingopenai/gpt-oss-120breachesopenai/*, which answers 404Output in before_collide.txt
Narrower, exact, and many-
*routes/model/infolistsopenai/gpt-4o-minitwice andopenai/gpt-4.1-minitwiceOutput in before_narrow.txt
openai/*lists thegpt-4.1ids anopenai/gpt-4.1*route serves, and one worksOutput in before_narrow.txt
/v1/modelsanswers promptly next tostars/*******!andgstars/********groutesOutput in before_narrow.txt
gstars/*listsgstars/gpt-4o-minitwice, thoughgstars/********gserves it and sends it asopenai/g, so the call failsOutput in before_narrow.txt
wide/,cust/, andaz/list the ids that a narrower route sends to the same model by another path, on both endpointsOutput in before_same.txt
wide/*andcust/*lists those ids, and they workOutput in before_same.txt
az/*uploads a batch file for Azure, and thewide/gpt-4o*health check reports healthyOutput in before_same.txt
Key scoped to wide routes beside OpenRouter and cross-region routes
orr/gpt-4oids and 66brk/anthropic.ids on/v1/modelsOutput in before_xr.txt
orr/*andbrk/*lists 33brk/anthropic.ids on/v1/modelsand 66 on/model/info, and the set oforr/gpt-4oandbrk/anthropic.ids it lists matches admin'sOutput in before_xr.txt
orr/gpt-4o-miniandbrk/anthropic.claude-haiku-4-5-20251001-v1:0, and both calls return 200Output in before_xr.txt
Key scoped to the partial routes
/v1/models,/model/info, or/model_group/info, and 4 Gemini 2.5 idsOutput in before_db.txt
Output in before_db.txt
Team routes beside a proxy-wide route
wide/claude-*andtw/claude-*routes overanthropic/claude-*through/model/new, beside the proxy-widewide/*Output in before_team.txt
wide/*lists nowide/claude-id on/v1/modelsand/model_group/infoOutput in before_team.txt
wide/claude-and 22tw/claude-ids on both endpoints, and callingwide/claude-haiku-4-5reaches OpenAI, which answers 404Output in before_team.txt
tw/*lists notw/claude-id, though callingtw/claude-haiku-4-5worksOutput in before_team.txt
Star routes
*overgemini/gemini-2.5*lists 4gemini/ids on both endpoints, and a listed one answers 404Output in before_star_gemini.txt
*overcloudflare/@cf/*lists no id on either endpointOutput in before_alias.txt
*overopenai/*listsopenai/gpt-4o-mini, which answers 400, while the unlistedgpt-4o-miniworksOutput in before_star_openai.txt
Bare model with custom_llm_provider
openai/*over a bare*withcustom_llm_provider: openailists 1442 ids on both endpoints, 75 of themopenai/claude...oropenai/gemini...ids, and callingopenai/claude-fable-5answers 404Output in before_bare.txt
g4/*over a baregpt-4*lists nog4/id on/v1/models, though callingg4/o-miniworks, as doesopenai/gpt-4o-miniOutput in before_bare.txt
Duplicates
Output in before_static.txt
Explicit id that a wildcard also covers
/v1/modelslistsgenai/test/gpt-4o-minitwiceOutput in before_alias.txt
Live listing
check_provider_endpoint: true, neither route lists an idOutput in before_live.txt
Unchanged routes
vertex_ai/gemini-*lists 31 ids, anddatabricks/system.ai.*lists noneOutput in before_static.txt
After (bb2b5b1)
Cloudflare partial wildcard
/v1/modelslists 34 Cloudflare idsOther cases
Gemini 2.5 partial wildcard
/v1/modelslists all 10 idsOutput in after_static.txt
Output in after_static.txt
Output in after_static.txt
Fireworks partial wildcard
/v1/modelslists 29 idsOutput in after_static.txt
Output in after_static.txt
Alias that rewrites part of the model name
custom/gemini-*overgemini/*lists 73 ids, and a listed one worksOutput in after_static.txt
team/*overanthropic/claude-*lists 22 ids liketeam/fable-5, and a listed one worksOutput in after_alias.txt
Alias that renames only the namespace
genai/test/*overopenai/*lists 208 ids, each once and none keeping theopenai/prefix, and a listed one worksOutput in after_alias.txt
team/claude-*overanthropic/claude-*lists the same 22 ids, and a listed one worksOutput in after_static.txt
Route-type infix, prefix alias, and fixed target
fixed/*lists 1 id, and the alias row carries the routed model and its priceOutput in after_routes.txt
Output in after_routes.txt
nova/,claude_platform/, andmantle/routes keep their ids, a listed one works, and the health check reports the route healthyOutput in after_infix.txt
Route shadowed by a more specific one
*overgroq/*still lists noopenai/gpt-ossid and no/model/inforow for it, sinceopenai/*serves that nameOutput in after_collide.txt
Narrower, exact, and many-
*routes/model/infolistsopenai/gpt-4o-minionce andopenai/gpt-4.1-minionceOutput in after_narrow.txt
openai/*lists thegpt-4.1ids anopenai/gpt-4.1*route serves, and one worksOutput in after_narrow.txt
/v1/modelsanswers promptly next tostars/*******!andgstars/********groutesOutput in after_narrow.txt
gstars/*no longer listsgstars/gpt-4o-mini, sincegstars/********gsends it asopenai/gOutput in after_narrow.txt
wide/,cust/, andaz/list the ids that a narrower route sends to the same model by another path, on both endpointsOutput in after_same.txt
wide/*andcust/*lists those ids, and they workOutput in after_same.txt
az/*uploads a batch file for Azure, and thewide/gpt-4o*health check reports healthyOutput in after_same.txt
Key scoped to wide routes beside OpenRouter and cross-region routes
orr/gpt-4oids and 25brk/anthropic.ids on/v1/modelsOutput in after_xr.txt
orr/*andbrk/*lists 25brk/anthropic.ids on/v1/modelsand 25 on/model/info, and the set oforr/gpt-4oandbrk/anthropic.ids it lists matches admin'sOutput in after_xr.txt
orr/gpt-4o-miniandbrk/anthropic.claude-haiku-4-5-20251001-v1:0, and both calls return 200Output in after_xr.txt
Key scoped to the partial routes
Output in after_db.txt
Output in after_db.txt
Team routes beside a proxy-wide route
/model/newOutput in after_team.txt
wide/*still lists nowide/claude-id on/v1/modelsand/model_group/infoOutput in after_team.txt
wide/claude-id, sincewide/*sends those ids to OpenAI, wherewide/claude-haiku-4-5still answers 404, and keeps its 22tw/claude-idsOutput in after_team.txt
tw/*lists 22tw/claude-ids, and callingtw/claude-haiku-4-5worksOutput in after_team.txt
Star routes
*overgemini/gemini-2.5*lists the bare ids the route takes, and a listed one worksOutput in after_star_gemini.txt
*overcloudflare/@cf/*lists 34 bare ids, and a listed one worksOutput in after_alias.txt
*overopenai/*listsgpt-4o-mini, which worksOutput in after_star_openai.txt
Bare model with custom_llm_provider
openai/*over a bare*lists 208 ids on both endpoints, none of themopenai/claude...oropenai/gemini...ids, and the listedopenai/gpt-4o-miniworksOutput in after_bare.txt
g4/*over a baregpt-4*lists 33 ids on both endpoints,g4/o-miniamong them, and calling it worksOutput in after_bare.txt
Duplicates
Output in after_static.txt
Explicit id that a wildcard also covers
/v1/modelslistsgenai/test/gpt-4o-minionceOutput in after_alias.txt
Live listing
check_provider_endpoint: true, both routes list ids from Gemini's live listOutput in after_live.txt
Output in after_live.txt
Unchanged routes
vertex_ai/gemini-*lists the same 31 ids, anddatabricks/system.ai.*lists noneOutput in after_static.txt
Output in after_static.txt
Audit round 3 of 3, PASS at ef70f84 (row 24 fixed past the cap)
Audit round 3 of 3 (row 24 fixed past the cap in 743790b)
Round 3 failed on row 24 at fa613b1. The fix went in past the cap in 743790b, since the failing row broke the PR's own same-model rule, and main was merged in at ef70f84. Every head cell ran again twice at ef70f84, rows 14 to 17 once more each with one worker. The base column is round 3's run at f9d45f8 with the head's test files, and the merge base is now b6ca57b. T is
tests/integration/routing/test_wildcard_partial_prefix_expansion.pyand A istests/integration/authorization/test_wildcard_model_access.py. Each leg had its own two-worker proxy, Postgres database, Redis and scripted upstream, and nothing inside the proxy was mocked. Both head runs used seed 43643 and order seed 997| # | Contract | Cell | Base | Head 1 | Head 2 | Result |
| 1 | Partial wildcard lists ids once | T::test_partial_wildcard_lists_provider_ids_once_on_both_endpoints | red | green | green | PASS |
| 2 | No-match partial lists nothing | T::test_partial_wildcard_matching_no_known_id_lists_no_guessed_ids | green | green | green | PASS |
| 3 | Provider wildcard still expands | T::test_provider_wildcard_still_expands_to_cost_map_names | green | green | green | PASS |
| 4 | Alias over a partial target | T::test_listed_wildcard_ids_reach_the_upstream_as_the_priced_model_their_row_names[alias_over_partial_model] | red | green | green | PASS |
| 5 | Alias over one fixed model | T::...[alias_over_one_fixed_model] | red | green | green | PASS |
| 6 |
*overopenai/*| T::...[catch_all_over_provider] | red | green | green | approved || 7 | Narrower route owns its rows | T::test_each_wildcard_row_names_the_deployment_its_id_routes_to[narrower_wildcard] | red | green | green | PASS |
| 8 | Exact deployment owns its row | T::...[exact_name] | red | green | green | PASS |
| 9 | Same model, same path | T::test_key_scoped_to_a_wildcard_lists_an_id_a_narrower_route_serves_only_if_it_reaches_the_named_model[same_model] | green | green | green | PASS |
| 10 | Narrower route, other model | T::...[different_model] | red | green | green | approved |
| 11 | Overlapping deployments list once | T::test_overlapping_deployments_of_one_wildcard_list_each_id_once | red | green | green | PASS |
| 12 | Health probe inside the pattern | T::test_wildcard_health_check_probes_a_model_its_pattern_names[gpt-4o*-True] | red | green | green | PASS |
| 13 | Health probe with no match | T::...[no-such-model-*-False] | green | green | green | PASS |
| 14 | /v1/models time,
!tail | T::test_wildcard_listing_answers_promptly_beside_a_route_with_many_stars[tail_in_no_listed_id-v1_models] | green | green | green | PASS || 15 | /model/info time,
!tail | T::...[tail_in_no_listed_id-model_info] | green | green | green | PASS || 16 | /v1/models time,
gtail | T::...[tail_early_in_listed_ids-v1_models] | green | green | green | PASS || 17 | /model/info time,
gtail | T::...[tail_early_in_listed_ids-model_info] | green | green | green | PASS || 18 | Same model via
responses/| T::...[same_model_through_responses_route] | green | green | green | PASS || 19 | Same model, bare plus provider | T::...[same_model_as_bare_model_with_provider] | green | green | green | PASS |
| 20 | Same model, other provider | T::...[same_model_on_another_provider] | green | green | green | PASS |
| 21 | Health falls back to provider | T::test_wildcard_health_check_stays_healthy_when_every_model_its_pattern_names_fails_but_the_provider_answers | green | green | green | PASS |
| 22 | Team files use narrow credentials | T::test_team_file_listing_takes_the_credentials_of_a_narrower_wildcard_route_on_the_requested_provider | green | green | green | PASS |
| 23 |
openai/*key beside*| A::test_a_key_scoped_to_a_provider_wildcard_on_the_catch_all_lists_only_ids_that_reach_the_upstream_as_named | red | green | green | approved || 24 | Same model under a vendor namespace | T::...[same_model_under_a_vendor_namespace] | green | green | green | PASS |
Controls ran once at ef70f84 and all four passed,
tests/integration/providers/test_coralbricks_provider_wire.py::test_wildcard_deployment_lists_every_coralbricks_catalog_rowand::test_wildcard_coralbricks_deployment_routes_the_requested_model, plus A::test_an_all_models_key_reaches_a_model_served_only_by_the_catch_all_deployment and A::test_a_key_without_models_inherits_the_users_exact_and_wildcard_grantsRow 24 now passes on both head runs. A key scoped to
{ns}/*lists{ns}/gpt-4.1beside{ns}/gpt-4.1*overopenrouter/openai/gpt-4.1*, /model/info has its row, and the call reaches the upstream asopenai/gpt-4.1. The same commit lets a key scoped to a wide wildcard list the ids a narrower route under its pattern serves, which the separate live risk check had flagged forbrk/*besidebrk/anthropic.*Row 23 lists no id for a key scoped to
openai/*beside*overopenai/*, which is correct, since that route sendsopenai/<id>upstream unchanged. The base lists those ids and failsRows 6, 10 and 23 change what callers see, and each one is the listing change #46384 asks for. The health check on a route where every probe fails makes up to six probe calls where the base made three, which the Caveats section lists
Type
Bug Fix
Caveats (if any)
Medium
check_provider_endpoint: truelists from the provider's live model list insteadLow
*lists every route's ids plus the bare ids main already listed for it, so it sees more ids than admin*keys see/model/infowith 2600 idsbedrock/*key beside abedrock/converse/*route lists each model under both routes, as admin doesdatabricks/system.ai.*unhealthy/model/infokeeps one row per deployment for an explicit model a wildcard also covers, like main/model/infonames the wider route's deployment for an id a narrower route on another provider serves but cannot list, like mainFinal Attestation
Note
Medium Risk
Touches core proxy model discovery, API key scoping, and pattern routing with intentional listing behavior changes; mistakes could hide callable models or expose wrong ids, though coverage is heavy on unit and integration tests.
Overview
Fixes wildcard expansion on
/v1/modelsand/model/infoso each listed id is the name the route actually accepts and maps to a known cost-map model once, instead of guessing prefixes or duplicating rows when narrower routes exist.Listing logic moves into shared helpers (
model_matches_wildcard,split_route_type_prefix,strip_provider_family_prefix,model_name_at_provider) and a_WildcardListingpipeline inmodel_checks.py. Expansion now strips Azure/Bedrock route-type infixes, respects bare deployments withcustom_llm_provider, and skips ids whenPatternMatchRouter.routes_elsewhereshows a more specific route would send a different upstream model (while keeping ids when wide and narrow routes reach the same model). Team-only routes and overlapping wildcard keys get the same rules;/model/infoexpansion receivespattern_routerfor shadowing.Pattern router gains linear-time
matches_from_start/greedy_captures/fill_wildcardsplusshadowing_regex,serves,registers, androutes_elsewhereso listing stays fast beside many-*patterns and aligns with how wildcards are filled at request time.Wildcard health checks probe up to three models that match the deployment pattern (then fall back to cheapest provider models), so partial routes like
gpt-4o*are not marked unhealthy when only non-matching catalog entries fail.Listing output changes by design (#46384); routing behavior for successful calls is unchanged.
Reviewed by Cursor Bugbot for commit bb2b5b1. Bugbot is set up for automated code reviews on this repo. Configure here.