Research Assistant Workshop

[!NOTE] Kernfrage
Wie entsteht ein quellengebundener Research Assistant, der Fachartikel durchsucht, Aussagen belegt, Unsicherheit sichtbar macht und bei Bedarf menschliche Freigabe einholt?


Inhaltsverzeichnis

  1. Projektübersicht
  2. Zielprodukt
  3. Leitplanken
  4. Notebook-Struktur
  5. Vorbereitung
    1. API-Key speichern
    2. Basis-Pakete installieren
    3. API-Key laden und Umgebung prüfen
  6. Kapitel 1: StateGraph Basics
    1. Aufgabe 1.1: State definieren
    2. Aufgabe 1.2: Minimal-Nodes erstellen
    3. Aufgabe 1.3: Minimalen Graphen bauen
  7. Kapitel 2: Intent Routing
    1. Aufgabe 2.1: Router-Funktion definieren
    2. Aufgabe 2.2: Antwort-Nodes anlegen
    3. Aufgabe 2.3: Conditional Edges einbauen
  8. Kapitel 3: Fachartikel-Korpus & Retrieval
    1. Aufgabe 3.1: Korpus laden
    2. Aufgabe 3.2: Retrieval implementieren
    3. Aufgabe 3.3: Retrieval in den Graphen einbauen
  9. Kapitel 4: Checkpointing & Sessions
    1. Aufgabe 4.1: Checkpointer einrichten
    2. Aufgabe 4.2: Session-basierte Interaktion
  10. Kapitel 5: Research-Synthese, Quellen und Freigabe
    1. Aufgabe 5.1: Antwortschema definieren
    2. Aufgabe 5.2: Qualitäts-Gate ergänzen
    3. Aufgabe 5.3: Testfragen definieren
  11. Kapitel 6: UI, Evaluation und Ausbau
    1. Option A: Gradio UI
    2. Option B: Eval-Set
    3. Option C: Multi-Agent-Ausbau
  12. Bewertungskriterien
  13. Abgabe
    1. Checkliste
  14. Hilfreiche Ressourcen
  15. FAQ

Projektübersicht

In diesem Praxisprojekt entsteht schrittweise ein Research Assistant für Fachartikel. Ausgangspunkt ist Pia: Sie muss regelmäßig neue Fachartikel sichten, sucht relevante Passagen und will Vorschläge prüfen, statt einem vollautomatischen System blind zu vertrauen.

Der Assistant lädt einen PDF-Korpus, sucht semantisch nach relevanten Passagen, formuliert eine strukturierte Antwort mit Quellenangaben, erkennt Out-of-Corpus-Fragen und pausiert bei Unsicherheit für menschliche Prüfung.

Das Zielbild der Leitaufgabe ist in Research Assistant beschrieben.

Lernziele:

  • LangGraph State Machines für kontrollierte Antwortpfade einsetzen
  • einen Fachartikel-Korpus laden, strukturieren und durchsuchen
  • Retrieval-Ergebnisse in belegte Antworten überführen
  • Out-of-Corpus-Fragen sicher stoppen
  • Sessions mit Checkpointing speichern
  • Human-in-the-Loop für unsichere oder folgenreiche Antworten einbauen
  • optional eine kleine Gradio-Oberfläche bereitstellen

Arbeitsumgebung: Google Colab, Jupyter Notebook oder lokales Python

Voraussetzung: Grundlagen zu Tool Use, LangGraph, RAG, State, HITL und Evaluation


Zielprodukt

Am Ende steht ein Assistant, der:

  1. einen PDF-Korpus reproduzierbar lädt,
  2. Dokumente in Chunks zerlegt und optional in ChromaDB einbettet,
  3. Fragen in natürlicher Sprache beantwortet,
  4. jede fachliche Antwort mit Dokumenttitel und Passagen-Zitat belegt,
  5. bei fehlendem Beleg “Nicht im Korpus” ausgibt,
  6. vor unsicheren finalen Antworten einen HITL-Schritt auslöst,
  7. optional als Supervisor-System mit Tabellen-Worker und Fließtext-Worker arbeitet.

Nicht erforderlich ist ein produktionsreifes Deployment. UI, LangSmith, Hugging Face Spaces und Multi-Agent-Ausbau sind Erweiterungen.


Leitplanken

Der Research Assistant ist ein Assistenzsystem, kein autonomes Entscheidungssystem.

Leitplanke Bedeutung
Quellenpflicht Fachliche Antworten brauchen Quellenangaben oder den Hinweis “Nicht im Korpus”.
Out-of-Corpus-Regel Fehlendes Wissen wird nicht frei erfunden.
HITL bei Unsicherheit Niedrige Sicherheit oder folgenreiche Aussagen werden vor der finalen Ausgabe geprüft.
Tool-Grenzen Tools laden, suchen, zitieren oder formatieren; offene Seiteneffekte brauchen Freigabe.
Datenschutz Beispiele nutzen öffentliche Fachtexte oder synthetische Daten.
Logging bewusst einsetzen Tracing hilft beim Debugging, sensible Inhalte werden aber nicht unbedacht protokolliert.

Notebook-Struktur

Zu erstellen ist ein Notebook mit 6 aufbauenden Kapiteln:

Research_Assistant_Workshop.ipynb
   ├── Kapitel 1: StateGraph Basics
   ├── Kapitel 2: Intent Routing
   ├── Kapitel 3: Fachartikel-Korpus & Retrieval
   ├── Kapitel 4: Checkpointing & Sessions
   ├── Kapitel 5: Research-Synthese, Quellen und Freigabe
   └── Kapitel 6: UI, Evaluation und Ausbau
Kapitel Schwerpunkt Ergebnis
1 StateGraph Basics Ein minimaler Graph mit State und Fallback
2 Intent Routing Fragen werden nach Typ geroutet
3 Korpus & Retrieval Relevante Passagen werden aus Fachartikeln geholt
4 Sessions Verlauf bleibt über Checkpointing erhalten
5 Synthese & HITL Antworten sind strukturiert, belegt und kontrollierbar
6 Ausbau UI, Evaluation oder Multi-Agent-Variante

Vorbereitung

API-Key speichern

Beim Arbeiten mit einem externen Modell den OPENAI_API_KEY in Colab Secrets oder lokal in einer .env speichern.

Basis-Pakete installieren

!uv pip install --system -q git+https://github.com/ralf-42/Agenten.git#subdirectory=04_modul
!uv pip install --system -q langgraph>=1.0.0 langgraph-checkpoint-sqlite langchain-chroma gradio pypdf

API-Key laden und Umgebung prüfen

from genai_lib.utilities import setup_api_keys, check_environment

setup_api_keys(["OPENAI_API_KEY"])
check_environment()

Kapitel 1: StateGraph Basics

Lernziel: Einen kleinen Graphen mit TypedDict-State und sicheren Fallbacks aufbauen.

Beispielfragen:

  • “Welche Methode schlägt Artikel A zur Chunking-Optimierung vor?”
  • “Vergleiche die Aussagen aus zwei Quellen.”
  • “Findet der Korpus etwas zu regulatorischen Risiken?”

Aufgabe 1.1: State definieren

from typing import Literal, TypedDict
from langgraph.graph import END, START, StateGraph

class ResearchState(TypedDict):
    user_query: str
    intent: Literal["factual", "comparison", "summary", "out_of_scope", "fallback"] | None
    retrieved_context: str
    sources: list[dict]
    confidence: Literal["hoch", "mittel", "niedrig"] | None
    needs_approval: bool
    answer: str

Aufgabe 1.2: Minimal-Nodes erstellen

def classify_intent(state: ResearchState) -> ResearchState:
    """Erkennt den Fragetyp."""
    query = state["user_query"].lower()
    # einfache Heuristik oder LLM-Klassifikation ergänzen
    return state

def fallback_answer(state: ResearchState) -> ResearchState:
    """Gibt eine sichere Fallback-Antwort zurück."""
    state["answer"] = "Ich kann diese Frage noch nicht zuverlässig aus dem Korpus beantworten."
    return state

Aufgabe 1.3: Minimalen Graphen bauen

workflow = StateGraph(ResearchState)
workflow.add_node("classify_intent", classify_intent)
workflow.add_node("fallback", fallback_answer)

workflow.add_edge(START, "classify_intent")
workflow.add_edge("classify_intent", "fallback")
workflow.add_edge("fallback", END)

graph = workflow.compile()

Erfolgskriterium:

  • Der Graph läuft fehlerfrei.
  • Der State wird korrekt befüllt.
  • Unklare Fragen erhalten eine sichere Antwort.

Kapitel 2: Intent Routing

Lernziel: Verschiedene Fragetypen über Conditional Edges zu spezialisierten Nodes leiten.

Aufgabe 2.1: Router-Funktion definieren

from typing import Literal

def route_by_intent(
    state: ResearchState,
) -> Literal["factual", "comparison", "summary", "out_of_scope", "fallback"]:
    return state["intent"] or "fallback"

Aufgabe 2.2: Antwort-Nodes anlegen

def answer_factual_question(state: ResearchState) -> ResearchState:
    """Beantwortet faktische Fragen mit Quellenbezug."""
    ...

def compare_sources(state: ResearchState) -> ResearchState:
    """Vergleicht Aussagen aus mehreren Quellen."""
    ...

def summarize_findings(state: ResearchState) -> ResearchState:
    """Fasst relevante Passagen aus dem Korpus zusammen."""
    ...

def handle_out_of_scope(state: ResearchState) -> ResearchState:
    """Stoppt Antworten, die nicht aus dem Korpus belegbar sind."""
    state["answer"] = "Nicht im Korpus."
    state["confidence"] = "niedrig"
    return state

Aufgabe 2.3: Conditional Edges einbauen

workflow.add_conditional_edges(
    "classify_intent",
    route_by_intent,
    {
        "factual": "factual",
        "comparison": "comparison",
        "summary": "summary",
        "out_of_scope": "out_of_scope",
        "fallback": "fallback",
    },
)

Erfolgskriterium:

  • Faktische Fragen, Vergleichsfragen, Zusammenfassungen und Out-of-Corpus-Fragen landen in unterschiedlichen Pfaden.
  • Der Graph endet deterministisch.

Kapitel 3: Fachartikel-Korpus & Retrieval

Lernziel: Fachartikel laden, in Passagen strukturieren und als belegbaren Kontext in den Graphen einbinden.

Aufgabe 3.1: Korpus laden

Der Beispielkorpus liegt im Repository unter 02_daten/01_text/korpus_research/. Im Notebook kann er reproduzierbar geladen werden:

from genai_lib.github import copy_from_github

KORPUS_QUELLE = "ralf-42/Agenten/02_daten/01_text"
KORPUS_MASKE = "fachartikel_*"
KORPUS_TARGET = "/content/files"

copy_from_github(KORPUS_QUELLE, KORPUS_TARGET, pattern=KORPUS_MASKE)

Jedes Dokument braucht mindestens:

  • Dateiname oder Titel
  • extrahierten Text
  • Chunk-ID oder Seitenhinweis
  • zitierfähige Passage

Aufgabe 3.2: Retrieval implementieren

Für eine Basisversion reicht Retrieval-Light mit einfacher Chunk-Suche. Die bessere Variante nutzt Embeddings, ChromaDB und Score-Thresholds.

def retrieve_context(state: ResearchState) -> ResearchState:
    """Sucht passende Passagen im Fachartikel-Korpus."""
    query = state["user_query"]
    # Chunks suchen, Scores prüfen, Quellen speichern
    ...
    return state

Aufgabe 3.3: Retrieval in den Graphen einbauen

workflow.add_node("retrieve_context", retrieve_context)

workflow.add_edge(START, "classify_intent")
workflow.add_edge("classify_intent", "retrieve_context")
workflow.add_conditional_edges("retrieve_context", route_by_intent, ...)

Erfolgskriterium:

  • Antworten nutzen belegte Passagen aus dem Korpus.
  • Quellen enthalten Dokumenttitel und Textausschnitt.
  • Bei schwachem Retrieval wird keine freie Antwort erfunden.

Kapitel 4: Checkpointing & Sessions

Lernziel: Verlauf und Sitzungen für wiederholte Recherchefragen speichern.

Aufgabe 4.1: Checkpointer einrichten

from langgraph.checkpoint.sqlite import SqliteSaver

checkpointer = SqliteSaver.from_conn_string("research_assistant_sessions.db")
graph = workflow.compile(checkpointer=checkpointer)

Aufgabe 4.2: Session-basierte Interaktion

config = {"configurable": {"thread_id": "pia_001"}}

result1 = graph.invoke(
    {
        "user_query": "Welche Studien vergleichen semantische Suche mit Stichwortsuche?",
        "intent": None,
        "retrieved_context": "",
        "sources": [],
        "confidence": None,
        "needs_approval": False,
        "answer": "",
    },
    config=config,
)

result2 = graph.invoke(
    {
        "user_query": "Welche Quelle war dafür am wichtigsten?",
        "intent": None,
        "retrieved_context": "",
        "sources": [],
        "confidence": None,
        "needs_approval": False,
        "answer": "",
    },
    config=config,
)

Erfolgskriterium:

  • Mehrere Anfragen gehören zur gleichen Session.
  • Verlauf kann eingesehen oder zurückgesetzt werden.
  • Der Assistant kann Folgefragen auf vorherige Recherche beziehen.

Kapitel 5: Research-Synthese, Quellen und Freigabe

Lernziel: Antworten strukturieren, Quellenpflicht erzwingen und unsichere Ergebnisse zur Freigabe markieren.

Aufgabe 5.1: Antwortschema definieren

from pydantic import BaseModel, Field

class Quellenangabe(BaseModel):
    dokument: str = Field(description="Dateiname oder Titel der Quelle")
    passage: str = Field(description="Zitierter Textausschnitt, maximal 2 Sätze")

class ResearchAntwort(BaseModel):
    antwort: str = Field(description="Synthese-Antwort auf die Frage")
    quellen: list[Quellenangabe] = Field(description="Mindestens eine Quellenangabe")
    sicherheit: str = Field(description="hoch / mittel / niedrig")
    hinweis: str = Field(description="'Nicht im Korpus' wenn out-of-scope")

Aufgabe 5.2: Qualitäts-Gate ergänzen

def quality_gate(state: ResearchState) -> ResearchState:
    """Markiert unsichere oder unbelegte Antworten für menschliche Prüfung."""
    if state["confidence"] == "niedrig" or not state["sources"]:
        state["needs_approval"] = True
        state["answer"] = "Freigabe erforderlich: Antwort ist nicht ausreichend belegt."
    return state

Der HITL-Schritt liegt nach der Synthese und vor der finalen Ausgabe.

Aufgabe 5.3: Testfragen definieren

Mindestens diese fünf Fälle testen:

  • Faktische Frage mit direkter Antwort im Korpus
  • Inferenzfrage, die mehrere Passagen zusammenführt
  • Vergleichsfrage zu zwei Quellen
  • Tabellen- oder Statistikfrage für einen spezialisierten Worker
  • Out-of-Corpus-Frage mit Antwort: “Nicht im Korpus”

Erfolgskriterium:

  • Antworten bleiben beim belegten Korpus.
  • Quellenangaben sind nachvollziehbar.
  • Out-of-Corpus-Fragen werden gestoppt.
  • HITL wird bei niedriger Sicherheit ausgelöst.
  • Testfragen laufen reproduzierbar durch.

Kapitel 6: UI, Evaluation und Ausbau

Lernziel: Den Assistant nutzbar machen und die Qualität systematisch prüfen.

Option A: Gradio UI

import gradio as gr

def chat_with_research_assistant(message, history, thread_id):
    config = {"configurable": {"thread_id": thread_id}}
    result = graph.invoke(
        {
            "user_query": message,
            "intent": None,
            "retrieved_context": "",
            "sources": [],
            "confidence": None,
            "needs_approval": False,
            "answer": "",
        },
        config=config,
    )
    return "", history + [(message, result["answer"])]

with gr.Blocks() as demo:
    gr.Markdown("# Research Assistant")
    thread_id = gr.Textbox(label="Session ID", value="pia_001")
    chatbot = gr.Chatbot(height=450)
    msg = gr.Textbox(placeholder="Frage zum Fachartikel-Korpus stellen ...")
    msg.submit(chat_with_research_assistant, [msg, chatbot, thread_id], [msg, chatbot])

Option B: Eval-Set

Ein kleines Eval-Set prüft Retrieval und Quellenbindung:

{
  "id": "q01",
  "frage": "Welche Methode wird in Dokument X zur Chunking-Optimierung vorgeschlagen?",
  "erwartete_passage": "...exakter Textausschnitt...",
  "quelle": "dateiname.pdf",
  "typ": "faktisch"
}

Sinnvolle Fragetypen:

  • faktische Fragen
  • Inferenzfragen
  • Vergleichsfragen
  • Negativbeispiele mit “Nicht im Korpus”

Option C: Multi-Agent-Ausbau

Supervisor
├── Tabellen-Worker   -> verarbeitet Tabellen, Werte und strukturierte Abschnitte
└── Fließtext-Worker  -> verarbeitet Prosa, Argumentationen und Zusammenfassungen

Der Supervisor analysiert den Fragetyp und delegiert. Bei Unsicherheit geht die Antwort nach der Synthese in die menschliche Freigabe.


Bewertungskriterien

Kriterium Mindestanforderung
Korpus PDF-Korpus wird reproduzierbar geladen oder dokumentiert eingebunden.
Retrieval Relevante Passagen werden besser gefunden als mit reinem Stichwortabgleich.
Antwortformat Ausgabe folgt einem strukturierten Schema mit Antwort, Quellen, Sicherheit und Hinweis.
Quellenbindung Mindestens eine Antwort enthält nachvollziehbare Quellenangaben.
Kontrolle HITL oder eine vergleichbare Freigabelogik ist erkennbar.
Sessions Checkpointing speichert mindestens eine wiederaufnehmbare Session.
Reflexion Grenzen, Risiken und nächste Verbesserungen werden benannt.

Bestanden: Der Assistant beantwortet belegbare Fragen aus dem Korpus, verweigert unbelegte Fragen kontrolliert und zeigt den Kontrollpunkt für unsichere Antworten.


Abgabe

Pflicht:

  • Research_Assistant_Workshop.ipynb
  • mindestens fünf dokumentierte Testfragen mit Ergebnissen
  • kurze Architekturübersicht, gern als Mermaid-Diagramm
  • kurze Reflexion zu Quellenbindung, Unsicherheit und Grenzen

Optional:

  • research_assistant_sessions.db
  • app.py für Gradio
  • kleines Eval-Set als JSON
  • Hugging Face Space oder anderes Demo-Deployment

Checkliste

  • Notebook läuft von oben bis unten fehlerfrei durch
  • StateGraph ist definiert
  • Intent-Routing funktioniert
  • Korpus oder ChromaDB ist eingebunden
  • Antworten enthalten Quellen
  • Out-of-Corpus-Fragen werden gestoppt
  • HITL oder Freigabelogik ist sichtbar
  • mindestens fünf Testfragen wurden ausgeführt

Hilfreiche Ressourcen

LangGraph:

Projektinterne Dokumente:


FAQ

Brauche ich vollständiges RAG?
Nein. Eine Basisversion darf mit Retrieval-Light starten. Für eine stärkere Lösung sind Embeddings, ChromaDB und Score-Thresholds sinnvoll.

Muss ich eine UI bauen?
Nein. Eine kleine Gradio-UI ist ein sinnvoller Ausbau, aber der Kern ist der kontrollierte Research-Assistant-Graph.

Muss ich Multi-Agent implementieren?
Nein. Für den Workshop reicht ein klarer Graph. Für die Challenge ist Supervisor plus zwei Worker die empfohlene Ausbauvariante.

Kann ich einen eigenen Korpus verwenden?
Ja. Der Korpus muss reproduzierbar geladen werden und genügend belegbare Fragen ermöglichen.

Was ist der wichtigste Qualitätsmaßstab?
Der Assistant darf fachliche Aussagen nur mit Quellenbezug ausgeben und muss fehlendes Wissen sichtbar begrenzen.


Version: 2.0
Stand: Mai 2026