From 508fafd0dfc571ef0a837a195ff7c2fc2180389d Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 14 Aug 2025 08:38:43 +0200 Subject: [PATCH] llm-api/llm_api.py aktualisiert --- llm-api/llm_api.py | 163 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 143 insertions(+), 20 deletions(-) diff --git a/llm-api/llm_api.py b/llm-api/llm_api.py index 06df246..86aa9b6 100644 --- a/llm-api/llm_api.py +++ b/llm-api/llm_api.py @@ -1,37 +1,160 @@ -from dotenv import load_dotenv -load_dotenv() # Lädt Variablen aus .env in os.environ +# -*- 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 clients import model, qdrant -from wiki_router import router as wiki_router -from embed_router import router as embed_router -from exercise_router import router as exercise_router -from plan_router import router as plan_router -from plan_session_router import router as plan_session_router +from fastapi.middleware.cors import CORSMiddleware -# Version -__version__ = "1.1.6" +# ---------------------- +# 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).", + }, +] - -# FastAPI-Instanz app = FastAPI( title="KI Trainerassistent API", - description="Modulare API für Trainingsplanung und MediaWiki-Import", + 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}, ) -# Globaler Fehlerhandler +# 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 -app.include_router(wiki_router) -app.include_router(embed_router) -app.include_router(exercise_router) -app.include_router(plan_router) -app.include_router(plan_session_router) \ No newline at end of file +# ---------------------- +# 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}