"""LLM provider detection and configuration."""
import os
from dataclasses import dataclass
import httpx
@dataclass
class ProviderConfig:
"""LLM provider configuration."""
type: str # "lm_studio", "openrouter", "openai"
name: str
api_url: str
api_key: str | None
model: str | None
class ProviderManager:
"""Detect and manage LLM providers."""
# LM Studio addresses to scan (host:port)
LM_STUDIO_ADDRESSES = [
"10.0.0.163:1235", # Local network LM Studio
"localhost:1234",
"localhost:1235",
"localhost:1236",
"localhost:8080",
"localhost:5000",
]
def detect(self) -> ProviderConfig | None:
"""Detect available LLM provider.
Priority: LM Studio > OpenRouter > OpenAI
"""
# Check for forced provider
forced = os.environ.get("VOICE_PROVIDER")
if forced:
if forced == "lm_studio":
return self._detect_lm_studio()
elif forced == "openrouter":
return self._detect_openrouter()
elif forced == "openai":
return self._detect_openai()
# Auto-detect
provider = self._detect_lm_studio()
if provider:
return provider
provider = self._detect_openrouter()
if provider:
return provider
provider = self._detect_openai()
if provider:
return provider
return None
def _detect_lm_studio(self) -> ProviderConfig | None:
"""Detect LM Studio on configured addresses."""
for address in self.LM_STUDIO_ADDRESSES:
url = f"http://{address}/v1"
try:
response = httpx.get(
f"{url}/models",
timeout=1.0,
)
if response.status_code == 200:
data = response.json()
models = data.get("data", [])
model = models[0]["id"] if models else None
return ProviderConfig(
type="lm_studio",
name=f"LM Studio ({address})",
api_url=url,
api_key=None,
model=model,
)
except (httpx.RequestError, httpx.TimeoutException):
continue
return None
def _detect_openrouter(self) -> ProviderConfig | None:
"""Detect OpenRouter via API key."""
api_key = os.environ.get("OPENROUTER_API_KEY")
if not api_key:
return None
return ProviderConfig(
type="openrouter",
name="OpenRouter",
api_url="https://openrouter.ai/api/v1",
api_key=api_key,
model=os.environ.get("OPENROUTER_MODEL", "anthropic/claude-3.5-sonnet"),
)
def _detect_openai(self) -> ProviderConfig | None:
"""Detect OpenAI via API key."""
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
return None
return ProviderConfig(
type="openai",
name="OpenAI",
api_url="https://api.openai.com/v1",
api_key=api_key,
model=os.environ.get("OPENAI_MODEL", "gpt-4o"),
)