⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

Commit 3da39e5

Browse files
Add model filter feature for providers
Features: - Add model_filter field to Provider schema for substring filtering - Filter models by name during sync (case-insensitive substring match) - Example: ":free" filters OpenRouter to only sync free models Database: - Add model_filter column to providers table with migration - Update expected_columns list for validation Backend: - Implement filtering in provider_sync.py after fetch - Log original count, filtered count, and filter pattern - Filter applied before model processing API: - Add model_filter parameter to create_provider and update_provider - Update provider endpoints to handle model_filter - Add model_filter to admin provider creation/update UI: - Add Model Filter input field to Add Provider form - Add Model Filter input field to Edit Provider form - Display filter in provider list (Admin page) - Load filter value when editing provider - Placeholder text explains usage (e.g., ":free") Use Case: - Filter OpenRouter models with ":free" to only sync free models - Reduce LiteLLM model count and improve performance - Only sync relevant models from large provider catalogs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent ff2a03e commit 3da39e5

File tree

7 files changed

+40
-1
lines changed

7 files changed

+40
-1
lines changed

backend/provider_sync.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@ async def sync_provider(session, config, provider, push_to_litellm: bool = True)
4040

4141
logger.info("Found %d models from %s", len(source_models.models), provider.name)
4242

43+
# Apply model filter if configured
44+
if provider.model_filter:
45+
original_count = len(source_models.models)
46+
filtered_models = [
47+
m for m in source_models.models
48+
if provider.model_filter.lower() in m.id.lower()
49+
]
50+
source_models.models = filtered_models
51+
logger.info(
52+
"Applied filter '%s': %d models matched (filtered out %d)",
53+
provider.model_filter,
54+
len(filtered_models),
55+
original_count - len(filtered_models)
56+
)
57+
4358
# Track active model IDs
4459
active_model_ids = set()
4560

frontend/api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ async def create_provider_legacy(
397397
api_key: str | None = Form(None),
398398
prefix: str | None = Form(None),
399399
default_ollama_mode: str | None = Form(None),
400+
model_filter: str | None = Form(None),
400401
session: AsyncSession = Depends(get_session)
401402
):
402403
"""Legacy endpoint for creating providers - redirects to API."""
@@ -408,7 +409,8 @@ async def create_provider_legacy(
408409
type_=type,
409410
api_key=api_key,
410411
prefix=prefix,
411-
default_ollama_mode=default_ollama_mode
412+
default_ollama_mode=default_ollama_mode,
413+
model_filter=model_filter
412414
)
413415
from fastapi.responses import RedirectResponse
414416
return RedirectResponse(url="/admin", status_code=303)

frontend/routes/providers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ async def update_provider_endpoint(
317317
api_key: str | None = Form(None),
318318
prefix: str | None = Form(None),
319319
default_ollama_mode: str | None = Form(None),
320+
model_filter: str | None = Form(None),
320321
tags: str | None = Form(None),
321322
access_groups: str | None = Form(None),
322323
sync_enabled: bool | None = Form(None),
@@ -340,6 +341,7 @@ async def update_provider_endpoint(
340341
api_key=_normalize_optional_str(api_key),
341342
prefix=_normalize_optional_str(prefix),
342343
default_ollama_mode=_normalize_optional_str(default_ollama_mode),
344+
model_filter=_normalize_optional_str(model_filter),
343345
tags=_parse_csv_list(tags),
344346
access_groups=_parse_csv_list(access_groups),
345347
sync_enabled=_parse_bool(sync_enabled),

frontend/templates/admin.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ <h2>Providers</h2>
157157
<strong>${p.name}</strong> — ${p.type} (${p.base_url})
158158
${!p.sync_enabled ? '<span style="color: orange; font-weight: bold;"> [SYNC DISABLED]</span>' : ''}
159159
${p.prefix ? `<br><span class="muted">Prefix: ${p.prefix}</span>` : ''}
160+
${p.model_filter ? `<br><span class="muted">Filter: ${p.model_filter}</span>` : ''}
160161
${p.default_ollama_mode ? `<br><span class="muted">Ollama Mode: ${p.default_ollama_mode}</span>` : ''}
161162
${p.tags && p.tags.length ? `<br><span class="muted">Tags: ${p.tags.join(', ')}</span>` : ''}
162163
${p.access_groups && p.access_groups.length ? `<br><span class="muted">Access Groups: ${p.access_groups.join(', ')}</span>` : ''}
@@ -266,6 +267,7 @@ <h2>Providers</h2>
266267
document.getElementById('edit-provider-type').value = provider.type;
267268
document.getElementById('edit-api-key').value = provider.api_key || '';
268269
document.getElementById('edit-prefix').value = provider.prefix || '';
270+
document.getElementById('edit-model-filter').value = provider.model_filter || '';
269271
document.getElementById('edit-default-ollama-mode').value = provider.default_ollama_mode || '';
270272
document.getElementById('edit-tags').value = (provider.tags || []).join(', ');
271273
document.getElementById('edit-access-groups').value = (provider.access_groups || []).join(', ');
@@ -340,6 +342,7 @@ <h3>Add Provider</h3>
340342
</label>
341343
<label>API Key <input type="text" name="api_key" /></label>
342344
<label>Prefix <input type="text" name="prefix" placeholder="e.g., mks-ollama" /></label>
345+
<label>Model Filter <input type="text" name="model_filter" placeholder="e.g., :free (only sync models containing this text)" /></label>
343346
<label>Tags <input type="text" name="tags" placeholder="Comma separated (e.g., team-a, prod)" /></label>
344347
<label>Access Groups <input type="text" name="access_groups" placeholder="Comma separated (e.g., compat, tts)" /></label>
345348
<label>
@@ -409,6 +412,7 @@ <h3>Edit Provider</h3>
409412
</label>
410413
<label>API Key <input type="text" id="edit-api-key" name="api_key" /></label>
411414
<label>Prefix <input type="text" id="edit-prefix" name="prefix" placeholder="e.g., mks-ollama" /></label>
415+
<label>Model Filter <input type="text" id="edit-model-filter" name="model_filter" placeholder="e.g., :free (only sync models containing this text)" /></label>
412416
<label>Tags <input type="text" id="edit-tags" name="tags" placeholder="Comma separated" /></label>
413417
<label>Access Groups <input type="text" id="edit-access-groups" name="access_groups" placeholder="Comma separated (e.g., compat, tts)" /></label>
414418
<label>

shared/crud.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ async def create_provider(
9696
pricing_profile: str | None = None,
9797
pricing_override: dict | None = None,
9898
auto_detect_fim: bool = True,
99+
model_filter: str | None = None,
99100
) -> Provider:
100101
"""Create a new provider."""
101102
if type_ == "ollama" and default_ollama_mode is None:
@@ -110,6 +111,7 @@ async def create_provider(
110111
sync_enabled=sync_enabled,
111112
pricing_profile=pricing_profile,
112113
auto_detect_fim=auto_detect_fim,
114+
model_filter=model_filter,
113115
)
114116
provider.tags_list = normalize_tags(tags)
115117
provider.access_groups_list = normalize_tags(access_groups)
@@ -142,6 +144,7 @@ async def update_provider(
142144
api_key: str | None = None,
143145
prefix: str | None = None,
144146
default_ollama_mode: str | None = None,
147+
model_filter: str | None = None,
145148
tags: list[str] | None = None,
146149
access_groups: list[str] | None = None,
147150
sync_enabled: bool | None = None,
@@ -164,6 +167,8 @@ async def update_provider(
164167
provider.default_ollama_mode = default_ollama_mode
165168
elif provider.type == "ollama" and provider.default_ollama_mode is None:
166169
provider.default_ollama_mode = "ollama_chat"
170+
if model_filter is not None:
171+
provider.model_filter = model_filter
167172
if tags is not None:
168173
provider.tags_list = normalize_tags(tags)
169174
if access_groups is not None:

shared/database.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ async def ensure_minimum_schema(engine: AsyncEngine) -> None:
101101
# Column already exists - this is expected and OK
102102
logger.info(f"auto_detect_fim column already exists (expected): {str(e)[:100]}")
103103

104+
# Add model_filter column to providers table
105+
try:
106+
await conn.exec_driver_sql("ALTER TABLE providers ADD COLUMN model_filter VARCHAR")
107+
logger.info("Added model_filter column to providers table")
108+
except Exception as e:
109+
# Column already exists - this is expected and OK
110+
logger.info(f"model_filter column already exists (expected): {str(e)[:100]}")
111+
104112
# Models.system_tags / user_tags / access_groups / sync_enabled / mapped_provider_id / mapped_model_id
105113
result = await conn.exec_driver_sql("PRAGMA table_info(models)")
106114
model_columns = {row[1] for row in result}
@@ -172,6 +180,8 @@ async def ensure_minimum_schema(engine: AsyncEngine) -> None:
172180
"pricing_profile",
173181
"pricing_override",
174182
"sync_enabled",
183+
"auto_detect_fim",
184+
"model_filter",
175185
"created_at",
176186
"updated_at",
177187
]

shared/db_models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class Provider(Base):
3131
pricing_override: Mapped[str | None] = mapped_column(Text, nullable=True) # JSON object
3232
sync_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
3333
auto_detect_fim: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
34+
model_filter: Mapped[str | None] = mapped_column(String, nullable=True) # Regex or substring filter for model names
3435
created_at: Mapped[datetime] = mapped_column(
3536
DateTime, default=lambda: datetime.now(UTC), nullable=False
3637
)

0 commit comments

Comments
 (0)