8.4 KiB
🌲 Crumbforest Code Tour
Dies ist dein "Reiseführer" durch den Code. Ziel ist es, jede Komponente zu verstehen, damit du sie als solide Basis für zukünftige Projekte nutzen kannst.
🗺️ High-Level Architektur (Der Wald von oben)
Der CrumbCore basiert auf FastAPI (Python) und folgt einer modularen Struktur:
app/main.py: Der Eingang (Root). Hier startet alles.app/routers/: Die Wegweiser. Jede Datei bedient einen URL-Bereich (z.B./admin,/api).app/services/: Die Arbeiter. Logik für RAG, Übersetzungen, Config-Loading.app/models/: Die Datenbank-Struktur (SQLAlchemy / raw SQL Schemas).app/templates/: Das Gesicht (Jinja2 HTML Templates) & Design.
🚀 1. Der Einstieg: app/main.py
Hier atmet das System.
Wichtige Konzepte:
- FastAPI Instanz:
app = FastAPI()ist der Kern. - Middleware:
SessionMiddleware(für Login-Cookies) undCORSMiddleware(Sicherheit). - Template Engine:
init_templates(app)lädt Jinja2. Unsererender-Funktion injiziert automatisch User, Sprache und Flash-Nachrichten in jedes Template. - Router Mounting: Am Ende der Datei werden die Module aus
routers/eingebunden (app.include_router(...)). Das hältmain.pyschlank.
Code-Lupe:
# app/main.py
# ... Imports ...
# Security Secret (wichtig für Cookies!)
SECRET = os.getenv("APP_SECRET", "dev-secret-change-me")
app = FastAPI()
# 1. Statische Dateien (CSS, Bilder) verfügbar machen
app.mount("/static", StaticFiles(directory="static"), name="static")
# 2. Session Middleware (Das Gedächtnis des Browsers)
app.add_middleware(SessionMiddleware, secret_key=SECRET, ...)
# 3. Template Renderer (Unsere "View Engine")
def init_templates(app: FastAPI):
# ... lädt "templates"-Ordner ...
# Definiert die "render"-Funktion, die wir überall nutzen
def render(req: Request, template: str, **ctx):
# ... Logik für Sprache (lang) und User-Context ...
return HTMLResponse(tpl.render(**base_ctx))
# 4. Router einbinden (Modularisierung)
app.include_router(home_router, prefix="/{lang}") # Multi-Language Support!
# ...
🚦 2. Das Routing: app/routers/
Hier entscheidet sich, wohin die Reise geht.
home.py: Öffentliche Seiten (/,/about,/crew). Lädt Inhalte multiligual.crumbforest_roles.py: Das Herzstück Phase 1. Zeigt Rollen-Dashboard und Chat. Prüftuser_group-Zugriff.auth.py(bzw. Login in Hauptdatei/Home): Verwaltet Login/Logout.diary_rag.py: API für das Tagebuch-RAG (Vector Search).
🧠 3. Die Intelligenz: app/services/
rag_service.py: Verbindet Qdrant (Vektordatenbank) mit LLMs. Hier passiert die Magie ("Suche Igel im Wald").localization.py: Lädtcharacters.de.jsonusw. und merget sie live in die Config.
💾 4. Die Daten: crumbforest_config.json
Unsere "Single Source of Truth" für Rollen und Gruppen. Statt Hardcoding in Python definieren wir hier:
- Wer darf was sehen? (
group_access) - Welche Farbe hat der Fuchs?
- Welches LLM nutzt die Eule?
Dies ist der Startpunkt. Welchen Bereich wollen wir als nächstes zerlegen? 🧐
🧠 5. Deep Dive: Der RAG Service (app/services/rag_service.py)
Hier wird Text zu Wissen. Wenn wir "Wald" sagen, müssen wir nicht nur Keyword-Matches finden, sondern Bedeutung.
Der Indexing-Workflow (index_post)
Wie kommt ein Blogpost oder Tagebucheintrag in das Gehirn?
- Hash-Check: Wir erstellen einen MD5-Hash des Inhalts. Ist er gleich wie in der DB? Überspringen! (Spart KI-Kosten).
- Chunking: Der Text wird mit
EmbeddingServicein Häppchen geteilt (z.B. 1000 Zeichen). - Embedding: Jedes Häppchen wird durch ein Embedding-Modell (OpenAI/Jina) geschickt. Das Ergebnis ist ein Vektor (eine Liste von Zahlen, z.B.
[0.1, -0.5, ...]). - Upsert in Qdrant: Wir speichern den Vektor + Metadaten (Textinhalt, Titel, Slug) in Qdrant.
- Tracking: In MariaDB (
post_vectorsTabelle) merken wir uns, welcher Post welche Vektoren hat (für Löschung/Updates).
Die Suche (query_with_rag)
Der magische Moment, wenn der User eine Frage stellt:
- Embed Query: Die Frage "Wer wohnt im Wald?" wird ebenfalls in einen Vektor verwandelt.
- Vektor-Suche (Semantic Search): Wir fragen Qdrant: "Welche Vektoren liegen in der Nähe dieses Frage-Vektors?". Dank Cosine-Similarity findet er Inhalte, die inhaltlich passen, auch ohne exakte Worte.
- Context Assembly: Wir nehmen die Top-3 gefundenen Text-Chunks und kleben sie zusammen.
- LLM Generation: Wir bauen einen Prompt für die KI:
Context: [Der Igel wohnt im Unterholz...] Frage: Wer wohnt im Wald? Antworte basierend auf dem Context. - Antwort: Die KI generiert die fertige Antwort.
Warum rag_service.py wichtig ist
Er entkoppelt die Logik:
- Der Router (
diary_rag.py) weiß nur: "Indexiere dies" oder "Frage das". - Der Service kümmert sich um die Details (Qdrant API, Datenbank-Sync, Hash-Prüfung). So bleibt der Controller sauber! 🧹✨
🔐 6. Deep Dive: Sicherheit & Auth (app/deps.py)
Wie unterscheiden wir Freund von Feind? Das "Dependency Injection" System von FastAPI regelt das elegant.
Der Wächter: current_user
In app/deps.py definieren wir Funktionen, die FastAPI vor jedem Request ausführt.
# app/deps.py
def current_user(req: Request):
# Holt das User-Objekt aus dem signierten Session-Cookie
return req.session.get("user")
def admin_required(user = Depends(current_user)):
# 1. Ist ein User da?
if not user:
raise HTTPException(status_code=401) # -> Login Page
# 2. Ist es ein Admin?
if user.get("role") != "admin":
raise HTTPException(status_code=403) # -> Verboten!
return user
Die Anwendung in Routern
Wir müssen Sicherheitschecks nicht in jede Funktion kopieren. Wir "injizieren" sie einfach:
# app/routers/admin.py
@router.get("/")
def admin_dashboard(
user: dict = Depends(admin_required) # <--- Hier passiert der Check!
):
# Wenn wir hier sind, IST der User garantiert ein Admin.
return render(..., user=user)
Session Security (in main.py)
Das Login-System basiert auf SessionMiddleware.
- Cookie: Der Browser bekommt ein Cookie namens
session. - Signierung: Das Cookie ist mit
APP_SECRETkryptografisch signiert. Der User kann den Inhalt lesen (Base64), aber nicht verändern. Wenn errole: adminreinschummelt, wird die Signatur ungültig und der Server ignoriert das Cookie. - HttpOnly: JavaScript kann das Cookie nicht stehlen.
- SameSite: Schützt vor CSRF-Attacken.
🎨 7. Das Frontend (app/templates/)
Wir nutzen Jinja2 (klassisches Server-Side Rendering) gepaart mit PicoCSS (minimalistisches CSS Framework).
Struktur
base.html: Das Skelett. Enthält<head>, Navigation und Footer. Alle anderen Seiten erben hiervon ({% extends "base.html" %}).crumbforest/: Templates für die Rollen-Ansicht und den Chat.pages/: Allgemeine Seiten (Login, Home).
Datenfluss
Wenn ein Router render(req, "login.html", error="Falsches PW") aufruft:
- Jinja2 lädt
login.html. - Es sieht
{% extends "base.html" %}und lädt erst das Skelett. - Es füllt die Blöcke (
{% block content %}) mit dem Login-Formular. - Es injiziert die Variable
erroran der passenden Stelle.
💾 8. Die Daten (app/models/ & SQL)
Hier wird es interessant. Wir nutzen Hybrid-Ansatz:
API-Validierung (Pydantic)
In app/models/ liegen Klassen wie QueryRequest oder User.
Das sind keine Datenbank-Tabellen! Sie dienen nur der Validierung von Input/Output.
Wenn jemand JSON an die API schickt, prüft FastAPI automatisch: "Fehlt das Feld 'question'? Ist 'limit' eine Zahl?".
Datenbank (Raw SQL)
Wir nutzen kein schwergewichtiges ORM (wie SQLAlchemy) für die Queries, sondern rohes SQL via pymysql.
Warum? Performance & Kontrolle.
- Zu sehen in:
app/services/rag_service.pyodermain.py. - Codeschnipsel:
cur.execute("SELECT * FROM users WHERE email=%s", (email,))
Das macht den Code extrem transparent. SQL ist die Wahrheit.
🎉 Herzlichen Glückwunsch! Du hast den Wald einmal durchquert. Du kennst jetzt:
- Den Einstieg (
main.py) - Die Logik (
routers&services) - Die Sicherheit (
deps.py) - Das Gesicht (
templates) - Das Gedächtnis (
models& SQL)
Bereit für Phase 2? 🚀