fix(proxy): use filtered router count for search pagination of DB-only models

Greptile review caught a pagination bug: _get_db_only_models used
len(all_models) (full unfiltered router list) for the take calculation.
When searching with a large router (e.g. 50 models, page size 50), the
take would be 0 even though only 1 router model matched the search,
causing DB-only models to never be fetched.

Fix: accept filtered_router_count parameter and use it for pagination.
Added regression test with 50 router models where only 1 matches search.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Julio Quinteros Pro 2026-02-26 16:52:08 -03:00
parent 2159261da4
commit bb176f4a10
2 changed files with 51 additions and 3 deletions

View file

@ -8885,6 +8885,7 @@ async def _get_db_only_models(
sort_by: Optional[str] = None,
page: int = 1,
size: int = 50,
filtered_router_count: Optional[int] = None,
) -> Tuple[List[Dict[str, Any]], int]:
"""
Fetch models from the database that are NOT already loaded in the router.
@ -8894,13 +8895,16 @@ async def _get_db_only_models(
those models still appear in the model list.
Args:
all_models: Models already in the router
all_models: Models already in the router (used to collect DB model IDs to exclude)
prisma_client: Prisma client for database queries
proxy_config: Proxy config for decrypting models
search: Optional search term (case-insensitive)
sort_by: Optional sort field
page: Current page number
size: Page size
filtered_router_count: Number of router models after search filtering.
When provided, used for pagination instead of len(all_models).
This ensures correct pagination when search reduces the visible router models.
Returns:
Tuple of (db_only_models, db_only_count)
@ -8936,8 +8940,8 @@ async def _get_db_only_models(
if db_only_count == 0:
return [], 0
# Determine how many to fetch
router_models_count = len(all_models)
# Use filtered count for pagination when searching, full count otherwise
router_models_count = filtered_router_count if filtered_router_count is not None else len(all_models)
models_needed_for_page = size * page
if sort_by:
@ -9018,6 +9022,7 @@ async def _apply_search_filter_to_models(
sort_by=sort_by,
page=page,
size=size,
filtered_router_count=len(filtered_router_models),
)
total_count = len(filtered_router_models) + db_only_count

View file

@ -258,3 +258,46 @@ class TestApplySearchFilterIncludesDbOnlyModels:
assert result == router_models
assert total_count is None
@pytest.mark.asyncio
async def test_search_pagination_uses_filtered_count(self):
"""When searching, pagination should use the filtered router count, not the full count.
Regression test: if the router has 50 models but only 1 matches search,
DB-only models should still be fetched (take should be based on 1, not 50).
"""
# 50 router models, but only "gpt-5" matches the search "gpt-5"
router_models = [_make_router_model(f"model-{i}", f"id-{i}", db_model=True) for i in range(49)]
router_models.append(_make_router_model("gpt-5", "id-49", db_model=True))
db_record = _make_db_record("gpt-5.1", "id-missing")
prisma_client = MagicMock()
prisma_client.db.litellm_proxymodeltable.count = AsyncMock(return_value=1)
prisma_client.db.litellm_proxymodeltable.find_many = AsyncMock(
return_value=[db_record]
)
decrypted_model = _make_router_model("gpt-5.1", "id-missing", db_model=True)
proxy_config = MagicMock()
proxy_config.decrypt_model_list_from_db = MagicMock(
return_value=[decrypted_model]
)
result, total_count = await _apply_search_filter_to_models(
all_models=router_models,
search="gpt-5",
page=1,
size=50,
prisma_client=prisma_client,
proxy_config=proxy_config,
)
model_names = [m["model_name"] for m in result]
# Only gpt-5 matches search from router, gpt-5.1 from DB
assert "gpt-5" in model_names
assert "gpt-5.1" in model_names
assert len(result) == 2
assert total_count == 2
# Verify find_many was actually called (not skipped due to bad take calculation)
prisma_client.db.litellm_proxymodeltable.find_many.assert_called_once()