Initial commit: Agentic OS troubleshooting platform
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>
This commit is contained in:
106
backend/app/services/model_catalog.py
Normal file
106
backend/app/services/model_catalog.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""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,
|
||||
}
|
||||
Reference in New Issue
Block a user