feat(setup): dynamic model discovery for local providers in wizard
Replace the static model text prompt with live API queries: - _fetch_local_models(): queries /v1/models (LM Studio, llama.cpp) or /api/tags (Ollama) and returns a questionary.select list - _fetch_lmstudio_available_models(): queries LM Studio's beta /api/v0/models to list downloaded-but-not-loaded models - _load_lmstudio_model(): tries /api/v0/models/load to load a model in-place; falls back to telling the user to load manually - Cloud providers keep the existing text-input behaviour Also replace hardcoded LMSTUDIO_MODEL in integration tests with a lmstudio_model fixture that queries the API at runtime and uses whichever model is currently loaded (skips if none). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -153,11 +153,95 @@ def _check_local_server(provider: Provider) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _fetch_local_models(provider: Provider) -> list[str]:
|
||||
"""Return currently loaded/available models from a local provider's API."""
|
||||
if not provider.base_url:
|
||||
return []
|
||||
try:
|
||||
if provider.id == "ollama":
|
||||
resp = httpx.get(f"{provider.base_url}/api/tags", timeout=3.0)
|
||||
resp.raise_for_status()
|
||||
return [m["name"] for m in resp.json().get("models", [])]
|
||||
else:
|
||||
resp = httpx.get(f"{provider.base_url}/models", timeout=3.0)
|
||||
resp.raise_for_status()
|
||||
return [m["id"] for m in resp.json().get("data", [])]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _fetch_lmstudio_available_models() -> list[str]:
|
||||
"""Return all downloaded (not necessarily loaded) models from LM Studio's beta API."""
|
||||
try:
|
||||
resp = httpx.get("http://localhost:1234/api/v0/models", timeout=3.0)
|
||||
resp.raise_for_status()
|
||||
return [m["id"] for m in resp.json().get("data", [])]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _load_lmstudio_model(model_id: str) -> bool:
|
||||
"""Attempt to load a model via LM Studio's beta API. Returns True on success."""
|
||||
try:
|
||||
resp = httpx.post(
|
||||
"http://localhost:1234/api/v0/models/load",
|
||||
json={"identifier": model_id},
|
||||
timeout=60.0,
|
||||
)
|
||||
return resp.is_success
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _choose_model(provider: Provider) -> str:
|
||||
model = questionary.text(
|
||||
"Model name:",
|
||||
default=provider.default_model,
|
||||
).ask()
|
||||
if provider.group != "Local":
|
||||
model = questionary.text("Model name:", default=provider.default_model).ask()
|
||||
if model is None:
|
||||
raise SystemExit(0)
|
||||
return model.strip()
|
||||
|
||||
_MANUAL = "__manual__"
|
||||
loaded = _fetch_local_models(provider)
|
||||
|
||||
if loaded:
|
||||
choices = loaded + [questionary.Choice("── Enter manually ──", value=_MANUAL)]
|
||||
selected = questionary.select("Select model:", choices=choices).ask()
|
||||
if selected is None:
|
||||
raise SystemExit(0)
|
||||
if selected != _MANUAL:
|
||||
return selected
|
||||
|
||||
elif provider.id == "lmstudio":
|
||||
console.print(" [yellow]No model currently loaded in LM Studio.[/yellow]")
|
||||
available = _fetch_lmstudio_available_models()
|
||||
if available:
|
||||
choices = available + [questionary.Choice("── Enter manually ──", value=_MANUAL)]
|
||||
selected = questionary.select(
|
||||
"Select a downloaded model to load:", choices=choices
|
||||
).ask()
|
||||
if selected is None:
|
||||
raise SystemExit(0)
|
||||
if selected != _MANUAL:
|
||||
console.print(f" Loading [bold]{selected}[/bold]...", end=" ")
|
||||
if _load_lmstudio_model(selected):
|
||||
console.print("[green]✓ Loaded[/green]")
|
||||
else:
|
||||
console.print(
|
||||
"[yellow]Could not load via API — "
|
||||
"please load the model manually in LM Studio.[/yellow]"
|
||||
)
|
||||
return selected
|
||||
else:
|
||||
console.print(Panel(
|
||||
"No models are loaded or downloaded in LM Studio.\n"
|
||||
"Open LM Studio → Local Server tab → load a model, then re-run setup.",
|
||||
border_style="yellow",
|
||||
))
|
||||
|
||||
else:
|
||||
console.print(f" [yellow]No models found at {provider.base_url}.[/yellow]")
|
||||
|
||||
model = questionary.text("Model name:", default=provider.default_model).ask()
|
||||
if model is None:
|
||||
raise SystemExit(0)
|
||||
return model.strip()
|
||||
|
||||
Reference in New Issue
Block a user