Zum Inhalt

Hook-Spezifikationen

Die 8 Hookspecs leben in backend/app/hookspecs.py. Jede Hookspec definiert den Aufruf-Vertrag; Plugins implementieren sie mit @hookimpl.

get_assessment_questions

@hookspec
def get_assessment_questions(lang: str) -> list[dict] | None:
    """Fragepack für die angefragte Sprache zurückgeben.

    Implementiert von: assessment-Plugin.
    Modus: Liste (nicht firstresult). Die Route nutzt
    aktuell das Ergebnis des ersten Plugins; ein zukünftiges
    "mehrere Frage-Packs"-Feature könnte Anbieter benannte
    Packs registrieren lassen.
    """

Return-Form:

[
  {
    "id": "q01",
    "type": "single" | "multi",
    "text": "...",
    "answers": [
      {"id": "a", "text": "...", "weights": {"deductive": 1.0}},
      ...
    ]
  },
  ...
]

calculate_profile

@hookspec(firstresult=True)
def calculate_profile(answers: list[dict]) -> dict[str, float]:
    """Rohantworten zu einem 6-Methoden-Profil aggregieren.

    Implementiert von: assessment-Plugin.
    firstresult: genau ein Plugin berechnet das Profil.
    """

Input-answers-Form:

[
  {"question_id": "q01", "answer_ids": ["a", "b"]},  # multi
  {"question_id": "q02", "answer_id": "c"},          # single
  ...
]

Return: dict[method, float] mit den sechs Methoden-Keys.

create_session_prompt

@hookspec(firstresult=True)
def create_session_prompt(
    project: dict,
    profile: dict,
    method: str,
    step: int,
    lang: str,
) -> str:
    """System-Prompt für eine (Methode, Schritt, Sprache)-Zelle.

    Implementiert von: session-Plugin.
    firstresult: genau ein Plugin komponiert den Prompt.
    """

Return: ein String, bereit als system-Rolle-Nachricht gesendet zu werden. Die Implementierung des Session-Plugins liest aus dem 42-Zellen-_PROMPTS-Dict und hängt einen Kontextblock an.

ai_complete

@hookspec(firstresult=True)
def ai_complete(
    messages: list[dict[str, Any]],
    model: str,
    api_key: str,
    max_tokens: int = 1024,
) -> str | None:
    """KI-Anbieter aufrufen, Assistant-Text zurückgeben.

    Implementiert von: ai-anthropic, ai-openai, ai-gemini.
    firstresult: das Plugin des passenden Anbieters liefert
    den Text; andere liefern None.
    """

Jedes Anbieter-Plugin prüft den model-Präfix (claude-*, gpt-*, gemini-*) und liefert den Assistant- Text, wenn ihm das Modell gehört. Nicht-passende Plugins liefern None, sodass firstresult zum nächsten durchfällt.

messages-Form (OpenAI-Stil):

[
  {"role": "system", "content": "..."},
  {"role": "user", "content": "..."},
  {"role": "assistant", "content": "..."},
  {"role": "user", "content": "..."},
]

Anbieter-Plugins normalisieren diese Form zu ihrer eigenen API (Anthropic trennt system raus; Gemini faltet es ein).

recommend_method_switch

@hookspec
def recommend_method_switch(
    history: list[dict],
    profile: dict,
) -> dict | None:
    """Switch-Empfehlung zurück oder None.

    Implementiert von: session-Plugin.
    Modus: Liste. Plugins liefern einzelne Empfehlungen; ein
    zukünftiger Arbiter wählt die höchste-Konfidenz-Nicht-
    None.
    """

Return-Form:

{
  "recommended": True,
  "to_method": "dialogic",
  "reason": "Drei Sessions stagnierendes Verständnis.",
  "confidence": 0.75,  # optional
}

None (oder {"recommended": False}) zurückgeben, wenn kein Wechsel angezeigt ist.

on_session_complete

@hookspec
def on_session_complete(
    session: dict,
    rating: dict,
) -> None:
    """Seiteneffekt-Hook: feuert beim Session-Ende.

    Implementiert von: tracking-Plugin (schreibt einen
    ProgressCommit).
    Modus: Liste. Jeder Subscriber läuft; Fehler werden
    gefangen und geloggt, rollen aber das Session-End nicht
    zurück.
    """

Fehler in diesem Hook DÜRFEN NICHT propagieren — der _fire_on_session_complete-Wrapper in backend/app/main.py fängt und loggt sie.

get_progress_summary

@hookspec
def get_progress_summary(
    project_id: str,
    db: Session,
) -> dict | None:
    """Eine Namespace-Slice der Fortschritts-Summary zurück.

    Implementiert von: tracking-Plugin (liefert die "tracking"-
    Namespace-Slice).
    Modus: Liste. Slices werden in der Route shallow-merged.
    """

Jedes Plugin liefert ein Dict, gekeyt auf einen eindeutigen Namespace. Das Tracking-Plugin liefert {"tracking": {...}, "step_evaluation": {...}}. Ein zukünftiges Stagnations-Detektor-Plugin könnte {"stagnation": {...}} liefern usw.

get_tool_recommendations

@hookspec
def get_tool_recommendations(
    profile: dict,
    lang: str,
    limit: int = 5,
) -> list[dict] | None:
    """Gerankte externe Tool-Empfehlungen zurück.

    Implementiert von: tools-Plugin.
    Modus: Liste. Die Route nutzt aktuell das Ergebnis des
    ersten Plugins; ein zukünftiger Multi-Source-Recommender
    könnte mergen.
    """

Return-Form:

[
  {
    "name": "Anki",
    "url": "https://apps.ankiweb.net/",
    "why": "...",
    "weight_keys": ["deductive", "error_based"],
    "score": 0.5
  },
  ...
]

firstresult vs Listen-Modus

Hookspec Modus Warum
get_assessment_questions Liste (effektiv firstresult) Aktuell ein Pack; künftig benannte Packs
calculate_profile firstresult Genau ein Algorithmus
create_session_prompt firstresult Genau ein Prompt-Komponist
ai_complete firstresult Der passende Anbieter besitzt den Aufruf
recommend_method_switch Liste Mehrere Plugins können vorschlagen
on_session_complete Liste Seiteneffekte fächern auf
get_progress_summary Liste Namespace-Slices mergen
get_tool_recommendations Liste (effektiv firstresult) Aktuell eine Quelle

Die Liste-Modi, die sich aktuell wie firstresult verhalten, sind bewusst so: der Vertrag ist offen für Multi-Plugin- Erweiterung, aber die v0.7.0-Implementierung nutzt nur das erste.