Skip to content

Commit b44aa79

Browse files
feat: exclude coder LLMs from model list via user-configurable patterns
Add excluded_model_patterns config option that filters models at fetch time (startup and refresh). Users can add patterns to lms_config/user_config.json which is .gitignored so it survives updates. Defaults to filtering embedding models only.
1 parent 9dcb286 commit b44aa79

7 files changed

Lines changed: 104 additions & 33 deletions

File tree

LMStudio.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
# Attempt to fetch models at startup
4141
_startup_server_url = _config_manager.get_server_url()
4242
_startup_timeout = _config_manager.get_timeout()
43-
initialize_model_cache(_startup_server_url, _startup_timeout)
43+
_startup_excluded_patterns = _config_manager.get_excluded_patterns()
44+
initialize_model_cache(_startup_server_url, _startup_timeout, excluded_patterns=_startup_excluded_patterns)
4445

4546
# Image resize options
4647
IMAGE_RESIZE_OPTIONS = [
@@ -460,13 +461,14 @@ def generate(
460461
config = _config_manager.get_config()
461462
server_url = _config_manager.get_server_url()
462463
timeout = _config_manager.get_timeout()
464+
excluded_patterns = config.get("excluded_model_patterns", [])
463465

464466
troubleshooting_lines.append(f"[INFO] Server: {server_url}")
465467
troubleshooting_lines.append(f"[INFO] Cached models: {get_cached_model_count()}")
466468

467469
# Handle model refresh request
468470
if refresh_models:
469-
success, message = refresh_model_cache(server_url, timeout)
471+
success, message = refresh_model_cache(server_url, timeout, excluded_patterns=excluded_patterns)
470472
if success:
471473
troubleshooting_lines.append(f"[INFO] Model refresh: {message}")
472474
else:

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ Edit `lms_config/user_config.json`:
5353
}
5454
```
5555

56+
## Model Exclusion Patterns
57+
58+
Exclude models from the dropdown by adding patterns to `lms_config/user_config.json`:
59+
```json
60+
{
61+
"excluded_model_patterns": ["embedding", "Qwen3-Coder", "codellama"]
62+
}
63+
```
64+
65+
- The default excludes all models containing **"embedding"**.
66+
- Patterns are matched as case-insensitive substrings against the full model identifier.
67+
- Use specific enough patterns to avoid accidentally excluding desired models (e.g., use `Qwen3-Coder` instead of just `coder`).
68+
- To find exact model names, check LM Studio's API: `http://127.0.0.1:1234/v1/models` (look at the `id` field).
69+
- The `user_config.json` file is gitignored so your settings survive updates.
70+
- Restart ComfyUI or toggle the **refresh_models** checkbox after changing patterns.
71+
5672
## Outputs
5773

5874
| Output | Description |

__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ async def _refresh_models_route(request):
2121
config_manager = ConfigManager()
2222
server_url = config_manager.get_server_url()
2323
timeout = config_manager.get_timeout()
24+
excluded_patterns = config_manager.get_excluded_patterns()
2425

25-
success, message = refresh_model_cache(server_url, timeout)
26+
success, message = refresh_model_cache(server_url, timeout, excluded_patterns=excluded_patterns)
2627
choices = get_model_choices()
2728
models = [m for m in choices if m != CUSTOM_MODEL_OPTION]
2829

lms_config/config_manager.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
import json
66
import logging
77
from pathlib import Path
8-
from typing import Dict, Any
8+
from typing import Dict, Any, List
9+
910

1011
logger = logging.getLogger("EA_LMStudio")
1112

1213
DEFAULT_CONFIG: Dict[str, Any] = {
1314
"server_host": "127.0.0.1",
1415
"server_port": 1234,
15-
"timeout_seconds": 5
16+
"timeout_seconds": 5,
17+
"excluded_model_patterns": ["embedding"],
1618
}
1719

1820

@@ -67,6 +69,31 @@ def get_timeout(self) -> float:
6769
config = self.get_config()
6870
return float(config.get("timeout_seconds", 5))
6971

72+
def get_excluded_patterns(self) -> List[str]:
73+
"""Get model exclusion patterns from config.
74+
75+
Returns a merged list: default patterns + user-specified additions.
76+
The "embedding" pattern is always included and cannot be removed.
77+
"""
78+
config = self.get_config()
79+
val = config.get("excluded_model_patterns")
80+
81+
# Start with defaults
82+
result: List[str] = list(DEFAULT_CONFIG["excluded_model_patterns"])
83+
84+
# Merge user patterns (type guard + dedup)
85+
if isinstance(val, list):
86+
for p in val:
87+
if isinstance(p, str) and p not in result:
88+
result.append(p)
89+
elif val is not None:
90+
logger.warning(
91+
"excluded_model_patterns in user_config.json is not a list. "
92+
"Using defaults + any valid entries."
93+
)
94+
95+
return result
96+
7097
def create_user_config_template(self) -> None:
7198
"""
7299
Create user config template file if it doesn't exist.
@@ -78,7 +105,8 @@ def create_user_config_template(self) -> None:
78105
"_instructions": "Modify values below to override defaults. Delete this file to reset.",
79106
"server_host": "127.0.0.1",
80107
"server_port": 1234,
81-
"timeout_seconds": 5
108+
"timeout_seconds": 5,
109+
"excluded_model_patterns": ["embedding"],
82110
}
83111
try:
84112
with open(self.user_config_path, 'w', encoding='utf-8') as f:

lms_config/default_config.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22
"_comment": "Default configuration reference. Do not edit. Create user_config.json to override.",
33
"server_host": "127.0.0.1",
44
"server_port": 1234,
5-
"timeout_seconds": 5
5+
"timeout_seconds": 5,
6+
"excluded_model_patterns": [
7+
"embedding"
8+
]
69
}

model_fetcher.py

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import re
66
import requests
77
import logging
8-
from typing import List, Tuple, Optional
8+
from typing import List, Optional
9+
910

1011
logger = logging.getLogger("EA_LMStudio")
1112

@@ -17,58 +18,73 @@
1718
# Constants
1819
CUSTOM_MODEL_OPTION = "-- Custom (enter below) --"
1920

20-
# Patterns to exclude from model list (embedding models, etc.)
21-
EXCLUDED_MODEL_PATTERNS = ("embedding",)
22-
2321

24-
def validate_model_identifier(model_id: str) -> Tuple[bool, str]:
22+
def validate_model_identifier(model_id: str) -> bool:
2523
"""
2624
Validate model identifier for safety.
2725
2826
Args:
2927
model_id: The model identifier string to validate.
3028
3129
Returns:
32-
Tuple of (is_valid, error_message). error_message is empty if valid.
30+
True if valid, False otherwise.
3331
"""
3432
if not model_id or not model_id.strip():
35-
return False, "Model identifier cannot be empty"
33+
return False
3634

3735
model_id = model_id.strip()
3836

3937
# Check for path traversal attempts
4038
if ".." in model_id:
41-
return False, "Model identifier contains invalid path sequence"
39+
return False
4240

4341
# Check reasonable length
4442
if len(model_id) > 256:
45-
return False, "Model identifier exceeds maximum length (256 characters)"
43+
return False
4644

4745
# Allow alphanumeric, hyphens, underscores, dots, colons, at signs, forward slashes
4846
# These are common in model names like "lmstudio-community/Meta-Llama-3.1-8B-Instruct-GGUF"
4947
# or "qwen2.5-7b@q4_k_m"
5048
if not re.match(r'^[\w\-.:@/]+$', model_id):
51-
return False, "Model identifier contains disallowed characters"
49+
return False
50+
51+
return True
5252

53-
return True, ""
5453

54+
def _is_excluded(model_id: str, excluded_patterns: List[str]) -> bool:
55+
"""Check if a model should be excluded based on configured patterns."""
56+
if not excluded_patterns or not model_id:
57+
return False
58+
model_id_lower = model_id.lower()
59+
return any(pattern in model_id_lower for pattern in excluded_patterns)
5560

56-
def fetch_models_from_server(server_url: str, timeout: float = 5.0) -> Tuple[List[str], Optional[str]]:
61+
62+
def fetch_models_from_server(
63+
server_url: str,
64+
timeout: float = 5.0,
65+
excluded_patterns: Optional[List[str]] = None,
66+
) -> tuple[List[str], Optional[str]]:
5767
"""
5868
Fetch available models from LM Studio server.
5969
6070
Args:
6171
server_url: Base URL of LM Studio server (e.g., http://127.0.0.1:1234)
6272
timeout: Request timeout in seconds
73+
excluded_patterns: List of substrings to exclude from model list.
74+
If None, uses default ["embedding"]. Pass an empty list to include all models.
6375
6476
Returns:
6577
Tuple of (model_list, error_message)
66-
- model_list: List of model IDs (excludes embedding models), empty list on failure
78+
- model_list: Filtered list of model IDs, empty on failure
6779
- error_message: None on success, descriptive error on failure
6880
"""
6981
models: List[str] = []
7082
error: Optional[str] = None
7183

84+
# Default to ["embedding"] if no patterns specified
85+
if excluded_patterns is None:
86+
excluded_patterns = ["embedding"]
87+
7288
endpoint = f"{server_url.rstrip('/')}/v1/models"
7389

7490
try:
@@ -88,15 +104,14 @@ def fetch_models_from_server(server_url: str, timeout: float = 5.0) -> Tuple[Lis
88104
if not model_id:
89105
continue
90106

91-
# Exclude embedding models by checking id pattern
92-
model_id_lower = model_id.lower()
93-
is_excluded = any(pattern in model_id_lower for pattern in EXCLUDED_MODEL_PATTERNS)
107+
# Exclude models matching configured patterns
108+
if _is_excluded(model_id, excluded_patterns):
109+
logger.debug(f"EA_LMStudio: Excluding model '{model_id}'")
110+
continue
94111

95-
if not is_excluded:
96-
# Validate the model ID before adding
97-
is_valid, _ = validate_model_identifier(model_id)
98-
if is_valid:
99-
models.append(model_id)
112+
# Validate the model ID before adding
113+
if validate_model_identifier(model_id):
114+
models.append(model_id)
100115

101116
# Sort alphabetically for easier navigation
102117
models.sort(key=str.lower)
@@ -139,20 +154,26 @@ def get_model_choices() -> List[str]:
139154
return choices
140155

141156

142-
def refresh_model_cache(server_url: str, timeout: float = 5.0) -> Tuple[bool, str]:
157+
def refresh_model_cache(
158+
server_url: str,
159+
timeout: float = 5.0,
160+
excluded_patterns: Optional[List[str]] = None,
161+
) -> tuple[bool, str]:
143162
"""
144163
Refresh the cached model list from server.
145164
146165
Args:
147166
server_url: Base URL of LM Studio server
148167
timeout: Request timeout in seconds
168+
excluded_patterns: List of substrings to exclude from model list.
169+
If None, uses default ["embedding"]. Pass an empty list to include all models.
149170
150171
Returns:
151172
Tuple of (success, message)
152173
"""
153174
global _cached_models, _last_fetch_error, _last_fetch_success
154175

155-
models, error = fetch_models_from_server(server_url, timeout)
176+
models, error = fetch_models_from_server(server_url, timeout, excluded_patterns)
156177

157178
if error:
158179
_last_fetch_error = error
@@ -169,15 +190,15 @@ def refresh_model_cache(server_url: str, timeout: float = 5.0) -> Tuple[bool, st
169190
return True, "Connected to LM Studio but no models found (embedding models are excluded)"
170191

171192

172-
def initialize_model_cache(server_url: str, timeout: float = 5.0) -> None:
193+
def initialize_model_cache(server_url: str, timeout: float = 5.0, excluded_patterns=None) -> None:
173194
"""
174195
Initialize model cache at startup. Silent failure - just logs warning.
175196
176197
Args:
177198
server_url: Base URL of LM Studio server
178199
timeout: Request timeout in seconds
179200
"""
180-
success, message = refresh_model_cache(server_url, timeout)
201+
success, message = refresh_model_cache(server_url, timeout, excluded_patterns=excluded_patterns)
181202
if not success:
182203
logger.warning(f"EA_LMStudio startup: {message}")
183204
logger.warning("EA_LMStudio: Models will need to be entered manually or refreshed later")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "EA_LMStudio"
33
description = "A custom node for LM Studio integration into ComfyUI."
4-
version = "1.2.3"
4+
version = "1.3.1"
55
license = {file = "LICENSE"}
66

77
[project.urls]

0 commit comments

Comments
 (0)