"""Curated LLM catalog: tiers, backends, and pricing hints.""" from __future__ import annotations from dataclasses import dataclass @dataclass(frozen=True) class ModelEntry: id: str backend: str # ollama | gemini | deepseek | openrouter model: str tier: str # local | economy | premium label: str input_per_m: float # USD per 1M input tokens output_per_m: float @property def provider(self) -> str: return "local" if self.backend == "ollama" else "api" @property def display(self) -> str: if self.backend == "ollama": return f"Local · Ollama · {self.model}" if self.backend == "gemini": return f"API · Gemini · {self.model}" if self.backend == "deepseek": return f"API · DeepSeek · {self.model}" return f"API · OpenRouter · {self.model}" # Curated models users can pick from the UI / config. CATALOG: list[ModelEntry] = [ # Local (Ollama) ModelEntry("local-qwen25-7b", "ollama", "qwen2.5:7b-instruct", "local", "Qwen 2.5 7B Instruct", 0, 0), ModelEntry("local-qwen25-coder-14b", "ollama", "qwen2.5-coder:14b", "local", "Qwen 2.5 Coder 14B", 0, 0), ModelEntry("local-llama32-3b", "ollama", "llama3.2:3b", "local", "Llama 3.2 3B", 0, 0), # Gemini economy (Google AI Studio — free tier eligible) ModelEntry("gemini-flash-25", "gemini", "gemini-2.5-flash", "economy", "Gemini 2.5 Flash", 0.0, 0.0), ModelEntry("gemini-flash-lite-25", "gemini", "gemini-2.5-flash-lite", "economy", "Gemini 2.5 Flash-Lite", 0.0, 0.0), ModelEntry("gemini-flash-20", "gemini", "gemini-2.0-flash", "economy", "Gemini 2.0 Flash", 0.0, 0.0), # Gemini premium ModelEntry("gemini-pro-25", "gemini", "gemini-2.5-pro", "premium", "Gemini 2.5 Pro", 0.0, 0.0), # DeepSeek economy ModelEntry("ds-chat", "deepseek", "deepseek-chat", "economy", "DeepSeek Chat", 0.14, 0.28), # DeepSeek premium ModelEntry("ds-reasoner", "deepseek", "deepseek-reasoner", "premium", "DeepSeek Reasoner", 0.55, 2.19), # OpenRouter economy ModelEntry("or-ds-chat", "openrouter", "deepseek/deepseek-chat", "economy", "DeepSeek Chat (OR)", 0.14, 0.28), ModelEntry("or-gemini-flash", "openrouter", "google/gemini-2.5-flash-preview", "economy", "Gemini 2.5 Flash", 0.15, 0.60), ModelEntry("or-gpt4o-mini", "openrouter", "openai/gpt-4o-mini", "economy", "GPT-4o Mini", 0.15, 0.60), ModelEntry("or-qwen25-72b", "openrouter", "qwen/qwen-2.5-72b-instruct", "economy", "Qwen 2.5 72B", 0.35, 0.40), # OpenRouter premium ModelEntry("or-claude-35-sonnet", "openrouter", "anthropic/claude-3.5-sonnet", "premium", "Claude 3.5 Sonnet", 3.0, 15.0), ModelEntry("or-claude-37-sonnet", "openrouter", "anthropic/claude-3.7-sonnet", "premium", "Claude 3.7 Sonnet", 3.0, 15.0), ModelEntry("or-gpt4o", "openrouter", "openai/gpt-4o", "premium", "GPT-4o", 2.5, 10.0), ] _BY_ID: dict[str, ModelEntry] = {m.id: m for m in CATALOG} def get_entry(entry_id: str) -> ModelEntry | None: return _BY_ID.get(entry_id) def entry_for_model(backend: str, model: str, tier: str = "economy") -> ModelEntry: """Find catalog entry or synthesize one for custom model IDs.""" for e in CATALOG: if e.backend == backend and e.model == model: return e label = model.split("/")[-1] if "/" in model else model pricing = (0.50, 1.50) if tier != "local" else (0.0, 0.0) return ModelEntry( id=f"custom-{backend}-{model.replace('/', '-')}", backend=backend, model=model, tier=tier, label=label, input_per_m=pricing[0], output_per_m=pricing[1], ) def catalog_by_tier() -> dict[str, dict[str, list[dict]]]: """Group catalog for API/UI: tier -> backend -> entries.""" out: dict[str, dict[str, list[dict]]] = { "local": {"ollama": []}, "economy": {"gemini": [], "deepseek": [], "openrouter": []}, "premium": {"gemini": [], "deepseek": [], "openrouter": []}, } for e in CATALOG: out[e.tier].setdefault(e.backend, []).append(_entry_dict(e)) return out def _entry_dict(e: ModelEntry) -> dict: return { "id": e.id, "backend": e.backend, "model": e.model, "tier": e.tier, "label": e.label, "input_per_m": e.input_per_m, "output_per_m": e.output_per_m, "display": e.display, }