-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
fix(studio): custom folder scan fails to find GGUF variants when pointing directly at a model directory #4860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
0c753ab
d955ee2
19f06ff
f5ebf10
a4e5673
7d4391f
57dfd81
e810fe2
a1e035a
d3f9545
0ddc5d0
44ac1f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -146,6 +146,39 @@ def _scan_models_dir( | |
| if not models_dir.exists() or not models_dir.is_dir(): | ||
| return [] | ||
|
|
||
| # Check if the directory itself IS a model (has a model config AND | ||
| # weight files). Both conditions are required: a bare directory with | ||
| # only loose .gguf files (no config) might be a mixed collection that | ||
| # should list files individually, and a config.json alone (no weights) | ||
| # does not make a model directory. | ||
| try: | ||
| _has_config = (models_dir / "config.json").exists() or ( | ||
| models_dir / "adapter_config.json" | ||
| ).exists() | ||
| _has_weights = any( | ||
| f.suffix.lower() in (".gguf", ".safetensors", ".bin") | ||
| for f in models_dir.iterdir() | ||
| if f.is_file() | ||
| ) | ||
| _is_self_model = _has_config and _has_weights | ||
| except OSError: | ||
| _is_self_model = False | ||
|
|
||
| if _is_self_model: | ||
| try: | ||
| updated_at = models_dir.stat().st_mtime | ||
| except OSError: | ||
| updated_at = None | ||
| return [ | ||
| LocalModelInfo( | ||
| id = str(models_dir), | ||
| display_name = models_dir.name, | ||
| path = str(models_dir), | ||
| source = "models_dir", | ||
| updated_at = updated_at, | ||
| ), | ||
| ] | ||
|
|
||
| found: List[LocalModelInfo] = [] | ||
| for child in models_dir.iterdir(): | ||
| if limit is not None and len(found) >= limit: | ||
|
|
@@ -243,6 +276,17 @@ def _scan_lmstudio_dir(lm_dir: Path) -> List[LocalModelInfo]: | |
| if not lm_dir.exists() or not lm_dir.is_dir(): | ||
| return [] | ||
|
|
||
| # If the directory itself is a model directory (has config files), | ||
| # it is not an LM Studio publisher structure -- _scan_models_dir | ||
| # already handles it. | ||
| try: | ||
| if (lm_dir / "config.json").exists() or ( | ||
| lm_dir / "adapter_config.json" | ||
| ).exists(): | ||
| return [] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This early return assumes Useful? React with 👍 / 👎. |
||
| except OSError: | ||
| pass | ||
|
|
||
| found: List[LocalModelInfo] = [] | ||
| for child in lm_dir.iterdir(): | ||
| try: | ||
|
|
@@ -263,6 +307,19 @@ def _scan_lmstudio_dir(lm_dir: Path) -> List[LocalModelInfo]: | |
| ) | ||
| continue | ||
|
|
||
| # If the child directory itself looks like a model (has config | ||
| # or model weight files), skip it -- _scan_models_dir already | ||
| # handles it. Only treat it as a publisher directory otherwise. | ||
| _child_is_model = ( | ||
| (child / "config.json").exists() | ||
| or (child / "adapter_config.json").exists() | ||
| or any(child.glob("*.safetensors")) | ||
| or any(child.glob("*.bin")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The _child_is_model = (
(child / "config.json").exists()
or (child / "adapter_config.json").exists()
or any(child.glob("*.safetensors"))
or any(child.glob("*.bin"))
or any(child.glob("*.gguf"))
) |
||
| or any(child.glob("*.gguf")) | ||
| ) | ||
| if _child_is_model: | ||
| continue | ||
|
|
||
| # child is a publisher directory -- scan its sub-directories | ||
| for model_dir in child.iterdir(): | ||
| try: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1047,8 +1047,13 @@ def list_local_gguf_variants( | |
| (variants, has_vision): list of non-mmproj GGUF variants + vision flag. | ||
| """ | ||
| p = Path(directory) | ||
| # If a file path was passed (e.g. a standalone .gguf entry), scan its | ||
| # parent directory instead so we still find the correct variants. | ||
| if not p.is_dir(): | ||
| return [], False | ||
| if p.is_file() and p.suffix.lower() == ".gguf": | ||
| p = p.parent | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This change returns variants when Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a standalone Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This fallback is a defensive safety net that only triggers for standalone .gguf file entries (Phase 2 of _scan_models_dir), i.e. directories without config.json For the loose-file scenario, the previous behavior was returning an empty array ("No GGUF variants found"), which was completely non-functional. Scanning the |
||
| else: | ||
| return [], False | ||
|
|
||
| quant_totals: dict[str, int] = {} | ||
| quant_first_file: dict[str, str] = {} | ||
|
|
@@ -1088,8 +1093,13 @@ def _find_local_gguf_by_variant(directory: str, variant: str) -> Optional[str]: | |
| Returns the resolved absolute path, or ``None`` if no match. | ||
| """ | ||
| p = Path(directory) | ||
| # If a file path was passed (e.g. a standalone .gguf entry), use its | ||
| # parent directory so variant lookup still works. | ||
| if not p.is_dir(): | ||
| return None | ||
| if p.is_file() and p.suffix.lower() == ".gguf": | ||
| p = p.parent | ||
| else: | ||
| return None | ||
|
|
||
| matches = sorted( | ||
| f | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This early return drops all nested models whenever the scan root itself has
config.json/weights. In mixed layouts (a root model plus additional model subfolders), the scanner now returns only the root entry, so valid child models disappear from custom-folder and LM Studio discovery results. Instead of returning immediately, add the root model tofoundand continue scanning children so both root and nested models are discoverable.Useful? React with 👍 / 👎.