Show all HF models individually in SWITCH LLM popup
Browse files- providers list now has 4 separate HF entries (Zephyr, Qwen, Llama, Mistral)
- SwitchProviderRequest accepts optional model field
- switch_llm_provider() forwards model to create_llm_client()
- /llm/providers returns current_model so UI can checkmark the right row
- active checkmark matches on both provider id + model
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- src/soci/api/routes.py +14 -9
- src/soci/api/server.py +3 -3
- web/index.html +7 -4
src/soci/api/routes.py
CHANGED
|
@@ -263,24 +263,29 @@ async def get_conversations(include_history: bool = True, limit: int = 20):
|
|
| 263 |
|
| 264 |
class SwitchProviderRequest(BaseModel):
|
| 265 |
provider: str
|
|
|
|
| 266 |
|
| 267 |
|
| 268 |
@router.get("/llm/providers")
|
| 269 |
async def get_llm_providers():
|
| 270 |
"""Return available LLM providers (those with API keys set) and the current one."""
|
| 271 |
import os
|
| 272 |
-
from soci.api.server import get_llm_provider
|
| 273 |
current = get_llm_provider()
|
|
|
|
| 274 |
providers = []
|
| 275 |
if os.environ.get("ANTHROPIC_API_KEY"):
|
| 276 |
-
providers.append({"id": "claude", "label": "Claude
|
| 277 |
if os.environ.get("GROQ_API_KEY"):
|
| 278 |
-
providers.append({"id": "groq", "label": "Groq
|
| 279 |
if os.environ.get("GEMINI_API_KEY"):
|
| 280 |
-
providers.append({"id": "gemini", "label": "Gemini 2.0 Flash",
|
| 281 |
-
providers.append(
|
| 282 |
-
providers.append(
|
| 283 |
-
|
|
|
|
|
|
|
|
|
|
| 284 |
|
| 285 |
|
| 286 |
@router.get("/llm/test")
|
|
@@ -309,8 +314,8 @@ async def set_llm_provider(req: SwitchProviderRequest):
|
|
| 309 |
if req.provider not in valid:
|
| 310 |
raise HTTPException(status_code=400, detail=f"Unknown provider '{req.provider}'")
|
| 311 |
try:
|
| 312 |
-
await switch_llm_provider(req.provider)
|
| 313 |
-
return {"ok": True, "provider": req.provider}
|
| 314 |
except Exception as e:
|
| 315 |
raise HTTPException(status_code=500, detail=str(e))
|
| 316 |
|
|
|
|
| 263 |
|
| 264 |
class SwitchProviderRequest(BaseModel):
|
| 265 |
provider: str
|
| 266 |
+
model: Optional[str] = None
|
| 267 |
|
| 268 |
|
| 269 |
@router.get("/llm/providers")
|
| 270 |
async def get_llm_providers():
|
| 271 |
"""Return available LLM providers (those with API keys set) and the current one."""
|
| 272 |
import os
|
| 273 |
+
from soci.api.server import get_llm_provider, get_simulation
|
| 274 |
current = get_llm_provider()
|
| 275 |
+
current_model = getattr(get_simulation().llm, "default_model", "")
|
| 276 |
providers = []
|
| 277 |
if os.environ.get("ANTHROPIC_API_KEY"):
|
| 278 |
+
providers.append({"id": "claude", "label": "Claude Haiku", "icon": "β", "model": ""})
|
| 279 |
if os.environ.get("GROQ_API_KEY"):
|
| 280 |
+
providers.append({"id": "groq", "label": "Groq Llama 8B", "icon": "β‘", "model": ""})
|
| 281 |
if os.environ.get("GEMINI_API_KEY"):
|
| 282 |
+
providers.append({"id": "gemini", "label": "Gemini 2.0 Flash", "icon": "β¦", "model": ""})
|
| 283 |
+
providers.append({"id": "hf", "model": "HuggingFaceH4/zephyr-7b-beta", "label": "HF Zephyr 7B", "icon": "π€"})
|
| 284 |
+
providers.append({"id": "hf", "model": "Qwen/Qwen2.5-7B-Instruct", "label": "HF Qwen 2.5 7B", "icon": "π€"})
|
| 285 |
+
providers.append({"id": "hf", "model": "meta-llama/Llama-3.2-3B-Instruct", "label": "HF Llama 3.2 3B", "icon": "π€"})
|
| 286 |
+
providers.append({"id": "hf", "model": "mistralai/Mistral-7B-Instruct-v0.3", "label": "HF Mistral 7B", "icon": "π€"})
|
| 287 |
+
providers.append({"id": "ollama", "label": "Ollama (local)", "icon": "π¦", "model": ""})
|
| 288 |
+
return {"current": current, "current_model": current_model, "providers": providers}
|
| 289 |
|
| 290 |
|
| 291 |
@router.get("/llm/test")
|
|
|
|
| 314 |
if req.provider not in valid:
|
| 315 |
raise HTTPException(status_code=400, detail=f"Unknown provider '{req.provider}'")
|
| 316 |
try:
|
| 317 |
+
await switch_llm_provider(req.provider, model=req.model or None)
|
| 318 |
+
return {"ok": True, "provider": req.provider, "model": req.model}
|
| 319 |
except Exception as e:
|
| 320 |
raise HTTPException(status_code=500, detail=str(e))
|
| 321 |
|
src/soci/api/server.py
CHANGED
|
@@ -57,14 +57,14 @@ def get_llm_provider() -> str:
|
|
| 57 |
return _llm_provider
|
| 58 |
|
| 59 |
|
| 60 |
-
async def switch_llm_provider(provider: str) -> None:
|
| 61 |
"""Hot-swap the LLM client on the running simulation."""
|
| 62 |
global _llm_provider, _simulation
|
| 63 |
assert _simulation is not None, "Simulation not initialized"
|
| 64 |
-
new_llm = create_llm_client(provider=provider)
|
| 65 |
_simulation.llm = new_llm
|
| 66 |
_llm_provider = provider
|
| 67 |
-
logger.info(f"LLM provider switched to: {provider} ({new_llm.__class__.__name__})")
|
| 68 |
|
| 69 |
|
| 70 |
async def simulation_loop(sim: Simulation, db: Database, tick_delay: float = 2.0) -> None:
|
|
|
|
| 57 |
return _llm_provider
|
| 58 |
|
| 59 |
|
| 60 |
+
async def switch_llm_provider(provider: str, model: Optional[str] = None) -> None:
|
| 61 |
"""Hot-swap the LLM client on the running simulation."""
|
| 62 |
global _llm_provider, _simulation
|
| 63 |
assert _simulation is not None, "Simulation not initialized"
|
| 64 |
+
new_llm = create_llm_client(provider=provider, model=model)
|
| 65 |
_simulation.llm = new_llm
|
| 66 |
_llm_provider = provider
|
| 67 |
+
logger.info(f"LLM provider switched to: {provider}/{model or 'default'} ({new_llm.__class__.__name__})")
|
| 68 |
|
| 69 |
|
| 70 |
async def simulation_loop(sim: Simulation, db: Database, tick_delay: float = 2.0) -> None:
|
web/index.html
CHANGED
|
@@ -3373,19 +3373,22 @@ document.getElementById('llm-model').addEventListener('click', async (e) => {
|
|
| 3373 |
existing.forEach(el => el.remove());
|
| 3374 |
|
| 3375 |
for (const p of data.providers) {
|
|
|
|
| 3376 |
const row = document.createElement('div');
|
| 3377 |
-
row.className = 'llm-opt' + (
|
| 3378 |
-
row.innerHTML = `<span class="llm-check">${
|
| 3379 |
<span style="font-size:15px">${p.icon}</span>
|
| 3380 |
<span>${p.label}</span>`;
|
| 3381 |
row.addEventListener('click', async () => {
|
| 3382 |
popup.style.display = 'none'; _llmPopupOpen = false;
|
| 3383 |
-
if (
|
| 3384 |
try {
|
|
|
|
|
|
|
| 3385 |
const r = await fetch(`${API_BASE}/llm/provider`, {
|
| 3386 |
method: 'POST',
|
| 3387 |
headers: {'Content-Type': 'application/json'},
|
| 3388 |
-
body: JSON.stringify(
|
| 3389 |
});
|
| 3390 |
if (!r.ok) { const err = await r.json(); showToast(`LLM switch failed: ${err.detail}`, 'event'); return; }
|
| 3391 |
showToast(`Switched to ${p.label}`, 'conv');
|
|
|
|
| 3373 |
existing.forEach(el => el.remove());
|
| 3374 |
|
| 3375 |
for (const p of data.providers) {
|
| 3376 |
+
const isActive = p.id === data.current && (p.model === data.current_model || (!p.model && p.id !== 'hf'));
|
| 3377 |
const row = document.createElement('div');
|
| 3378 |
+
row.className = 'llm-opt' + (isActive ? ' active' : '');
|
| 3379 |
+
row.innerHTML = `<span class="llm-check">${isActive ? 'β' : ''}</span>
|
| 3380 |
<span style="font-size:15px">${p.icon}</span>
|
| 3381 |
<span>${p.label}</span>`;
|
| 3382 |
row.addEventListener('click', async () => {
|
| 3383 |
popup.style.display = 'none'; _llmPopupOpen = false;
|
| 3384 |
+
if (isActive) return;
|
| 3385 |
try {
|
| 3386 |
+
const body = {provider: p.id};
|
| 3387 |
+
if (p.model) body.model = p.model;
|
| 3388 |
const r = await fetch(`${API_BASE}/llm/provider`, {
|
| 3389 |
method: 'POST',
|
| 3390 |
headers: {'Content-Type': 'application/json'},
|
| 3391 |
+
body: JSON.stringify(body),
|
| 3392 |
});
|
| 3393 |
if (!r.ok) { const err = await r.json(); showToast(`LLM switch failed: ${err.detail}`, 'event'); return; }
|
| 3394 |
showToast(`Switched to ${p.label}`, 'conv');
|