Files
mission-control-v2/backend/app.py
T
Hitonabi 45d635afae Fix: Re-Warm-Waechter + Startup-Warmup folgen dem aktiven Hirn (nicht mehr fix "hermes")
Nach dem Hirn-Wechsel auf fast (Qwen3.6) zeigte der Wächter noch auf "hermes"
(Hermes-4-14B) -> er haette das aus dem Warm-Set entfernte Modell wieder geladen.
warmer._brain_model() liest jetzt Hermes' model.default (Fallback fast); Startup-Warmup
BRAINS default "fast". Passt sich kuenftigen Hirn-Wechseln automatisch an.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 18:42:26 +02:00

91 lines
3.0 KiB
Python

"""
Mission Control 2.0 — dünner FastAPI-Einstieg.
Hängt die Router ein, liefert (in Prod) das gebaute React-Frontend aus und
setzt eine no-cache-Middleware. Im Dev läuft das Frontend über den Vite-Dev-
Server (proxyt /api hierher), daher CORS für localhost offen.
"""
import asyncio
import logging
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from starlette.requests import Request
from config import FRONTEND_DIST, VERSION
from routers import agent, connect, gateway_proxy, health, maintenance, memory, models, routing, system
from services import warmer
# Zentrales Logging — Level via MC_LOG_LEVEL (INFO default). Eine Konfiguration
# für alle Module (logging.getLogger(__name__)).
logging.basicConfig(
level=os.environ.get("MC_LOG_LEVEL", "INFO").upper(),
format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
)
log = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Hintergrund-Tasks an den App-Lebenszyklus binden: Re-Warm-Wächter fürs Agent-Hirn."""
task = asyncio.create_task(warmer.rewarm_loop()) if warmer.ENABLED else None
if task:
log.info("Hirn-Re-Warm-Wächter aktiv (Intervall %ss, Hirn dynamisch aus Hermes-Config)", warmer.INTERVAL)
try:
yield
finally:
if task:
task.cancel()
app = FastAPI(title="Mission Control 2.0", version=VERSION, lifespan=lifespan)
# Dev: Vite-Dev-Server (5173) ruft das Backend per /api auf.
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def no_cache(request: Request, call_next):
resp = await call_next(request)
if request.url.path.startswith("/api"):
resp.headers["Cache-Control"] = "no-cache"
return resp
app.include_router(health.router)
app.include_router(models.router)
app.include_router(routing.router)
app.include_router(system.router)
app.include_router(connect.router)
app.include_router(memory.router)
app.include_router(agent.router)
app.include_router(gateway_proxy.router) # OpenAI-kompatibler /v1-Gateway (model:auto)
app.include_router(maintenance.router)
# Prod: gebautes Frontend ausliefern (falls vorhanden). SPA-Fallback auf index.html.
if FRONTEND_DIST.exists():
app.mount("/assets", StaticFiles(directory=FRONTEND_DIST / "assets"), name="assets")
@app.get("/{full_path:path}")
def spa(full_path: str):
# Falls die Datei direkt in FRONTEND_DIST liegt (z.B. manifest.webmanifest, favicon.ico), liefere sie aus
target = FRONTEND_DIST / full_path
if target.is_file():
return FileResponse(target)
index = FRONTEND_DIST / "index.html"
if index.exists():
return FileResponse(index)
return {"detail": "frontend not built"}