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:
85
backend/app/api/llm.py
Normal file
85
backend/app/api/llm.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""LLM routing configuration API."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from app.core.deps import require_admin, require_readonly
|
||||
from app.models.user import User
|
||||
from app.schemas import LlmRoutingConfigIn, LlmRoutingConfigOut, ProviderModelsOut
|
||||
from app.services.model_catalog import catalog_by_tier, get_entry
|
||||
from app.services.model_config import (
|
||||
LlmRoutingConfig,
|
||||
available_backends,
|
||||
get_routing_config,
|
||||
save_routing_config,
|
||||
)
|
||||
from app.services.model_discovery import list_all_provider_models
|
||||
|
||||
router = APIRouter(prefix="/api/llm", tags=["llm"])
|
||||
|
||||
ROUTING_STRATEGY = (
|
||||
"Local Ollama first for triage and reasoning. On escalation, cloud providers are tried "
|
||||
"in order: Gemini API → DeepSeek → OpenRouter. Within each cost tier (economy, premium), "
|
||||
"each provider is attempted before moving to the next tier."
|
||||
)
|
||||
|
||||
_MODEL_ID_FIELDS = (
|
||||
"local_model_id",
|
||||
"economy_gemini_id",
|
||||
"economy_deepseek_id",
|
||||
"economy_openrouter_id",
|
||||
"premium_gemini_id",
|
||||
"premium_deepseek_id",
|
||||
"premium_openrouter_id",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/routing", response_model=LlmRoutingConfigOut)
|
||||
async def get_llm_routing(_: User = Depends(require_readonly)):
|
||||
cfg = await get_routing_config()
|
||||
return LlmRoutingConfigOut(
|
||||
config=cfg.to_dict(),
|
||||
catalog=catalog_by_tier(),
|
||||
available_backends=available_backends(),
|
||||
strategy=ROUTING_STRATEGY,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/models/available", response_model=ProviderModelsOut)
|
||||
async def get_available_models(_: User = Depends(require_readonly)):
|
||||
"""Live model lists from Ollama, Gemini, DeepSeek, and OpenRouter APIs."""
|
||||
data = await list_all_provider_models()
|
||||
return ProviderModelsOut(providers=data["providers"], routing_order=data["routing_order"])
|
||||
|
||||
|
||||
@router.put("/routing", response_model=LlmRoutingConfigOut)
|
||||
async def update_llm_routing(body: LlmRoutingConfigIn, _: User = Depends(require_admin)):
|
||||
current = await get_routing_config()
|
||||
data = body.model_dump(exclude_unset=True)
|
||||
|
||||
for field in (
|
||||
*_MODEL_ID_FIELDS,
|
||||
"auto_escalate",
|
||||
"max_tier",
|
||||
):
|
||||
if field in data and data[field] is not None:
|
||||
setattr(current, field, data[field])
|
||||
|
||||
if body.cloud_provider_order is not None:
|
||||
current.cloud_provider_order = body.cloud_provider_order
|
||||
|
||||
for fid in _MODEL_ID_FIELDS:
|
||||
val = getattr(current, fid)
|
||||
if val and not get_entry(val):
|
||||
raise HTTPException(status_code=400, detail=f"Unknown model id: {val}")
|
||||
|
||||
if current.max_tier not in ("local", "economy", "premium"):
|
||||
raise HTTPException(status_code=400, detail="max_tier must be local, economy, or premium")
|
||||
|
||||
await save_routing_config(current)
|
||||
return LlmRoutingConfigOut(
|
||||
config=current.to_dict(),
|
||||
catalog=catalog_by_tier(),
|
||||
available_backends=available_backends(),
|
||||
strategy=ROUTING_STRATEGY,
|
||||
)
|
||||
Reference in New Issue
Block a user