Trainer_LLM/llm-api/llm_api.py
Lars cadd23e554
All checks were successful
Deploy Trainer_LLM to llm-node / deploy (push) Successful in 1s
llm-api/llm_api.py aktualisiert
2025-09-03 13:00:16 +02:00

162 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
llm_api.py v1.2.0 (zentraler .env-Bootstrap, saubere Router-Einbindung, Swagger-Doku)
Änderungen ggü. v1.1.6:
- Zentrales .env-Bootstrapping VOR allen Router-Imports (findet Datei robust; setzt LLMAPI_ENV_FILE/LLMAPI_ENV_BOOTSTRAPPED)
- Konsistente Swagger-Beschreibung + Tags-Metadaten
- Router ohne doppelte Prefixe einbinden (die Prefixe werden in den Routern definiert)
- Root-/health und /version Endpoints
- Defensive Includes (Router-Importfehler verhindern Server-Absturz; Logging statt Crash)
- Beibehaltener globaler Fehlerhandler (generische 500)
Hinweis:
- wiki_router im Canvas (v1.4.2) nutzt bereits robustes .env-Loading, respektiert aber die zentral gesetzten ENV-Variablen.
- Wenn du ENV-Datei an anderem Ort hast, setze in der Systemd-Unit `Environment=LLMAPI_ENV_FILE=/pfad/.env`.
"""
from __future__ import annotations
import os
from pathlib import Path
from textwrap import dedent
from typing import Optional
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
# ----------------------
# Zentraler .env-Bootstrap (VOR Router-Imports ausführen!)
# ----------------------
def _bootstrap_env() -> Optional[str]:
try:
from dotenv import load_dotenv, find_dotenv
except Exception:
print("[env] python-dotenv nicht installiert überspringe .env-Loading", flush=True)
return None
candidates: list[str] = []
if os.getenv("LLMAPI_ENV_FILE"):
candidates.append(os.getenv("LLMAPI_ENV_FILE") or "")
fd = find_dotenv(".env", usecwd=True)
if fd:
candidates.append(fd)
candidates += [
str(Path.cwd() / ".env"),
str(Path(__file__).parent / ".env"),
str(Path.home() / ".env"),
str(Path.home() / ".llm-api.env"),
"/etc/llm-api.env",
]
for p in candidates:
try:
if p and Path(p).exists():
if load_dotenv(p, override=False):
os.environ["LLMAPI_ENV_FILE"] = p
os.environ["LLMAPI_ENV_BOOTSTRAPPED"] = "1"
print(f"[env] loaded: {p}", flush=True)
return p
except Exception as e:
print(f"[env] load failed for {p}: {e}", flush=True)
print("[env] no .env found; using process env", flush=True)
return None
_ENV_SRC = _bootstrap_env()
# ----------------------
# App + OpenAPI-Metadaten
# ----------------------
__version__ = "1.2.0"
print(f"[DEBUG] llm_api.py version {__version__} loaded from {__file__}", flush=True)
TAGS = [
{
"name": "wiki",
"description": dedent(
"""
MediaWiki-Proxy (Health, Login, Page-Info/Parse, SMW-Ask).
**ENV**: `WIKI_API_URL`, `WIKI_TIMEOUT`, `WIKI_RETRIES`, `WIKI_SLEEP_MS`, `WIKI_BATCH`.
"""
),
},
{
"name": "exercise",
"description": dedent(
"""
Übungen (Upsert, Suche, Delete). Upsert-Schlüssel: `external_id` (z. B. `mw:{pageid}`).
**ENV**: `EXERCISE_COLLECTION`, `QDRANT_HOST`, `QDRANT_PORT`.
"""
),
},
{
"name": "plans",
"description": "Trainingspläne (Templates/Generate/Export).",
},
]
app = FastAPI(
title="KI Trainerassistent API",
description=dedent(
f"""
Modulare API für Trainingsplanung und MediaWiki-Import.
**Version:** {__version__}
## Quickstart (CLI)
```bash
python3 wiki_importer.py --all
python3 wiki_importer.py --all --category "Übungen" --dry-run
```
"""
),
version=__version__,
openapi_tags=TAGS,
swagger_ui_parameters={"docExpansion": "list", "defaultModelsExpandDepth": 0},
)
# Optional: CORS für lokale UIs/Tools
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ----------------------
# Globaler Fehlerhandler (generisch)
# ----------------------
@app.exception_handler(Exception)
async def unicorn_exception_handler(request, exc):
return JSONResponse(status_code=500, content={"detail": "Interner Serverfehler."})
# ----------------------
# Router einbinden (WICHTIG: keine zusätzlichen Prefixe hier setzen)
# ----------------------
def _include_router_safely(name: str, import_path: str):
try:
module = __import__(import_path, fromlist=["router"]) # lazy import nach ENV-Bootstrap
app.include_router(module.router)
print(f"[router] {name} included", flush=True)
except Exception as e:
print(f"[router] {name} NOT included: {e}", flush=True)
_include_router_safely("wiki_router", "wiki_router") # prefix in Datei: /import/wiki
_include_router_safely("embed_router", "embed_router")
_include_router_safely("exercise_router", "exercise_router")
_include_router_safely("plan_router", "plan_router")
_include_router_safely("plan_session_router", "plan_session_router")
# ----------------------
# Basis-Endpunkte
# ----------------------
@app.get("/health", tags=["wiki"], summary="API-Health (lokal)")
def api_health():
return {"status": "ok"}
@app.get("/version", tags=["wiki"], summary="API-Version & ENV-Quelle")
def api_version():
return {"version": __version__, "env_file": _ENV_SRC}