Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend + LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini -> DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials, RBAC, audit log, project-memory + Obsidian integrations, and editable troubleshooting decision rules tuned for the GeneseasX vessel stack. Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
4.3 KiB
Python
107 lines
4.3 KiB
Python
"""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,
|
|
}
|