LangGraph Tutorial: Multiagentensysteme Schritt für Schritt
LangGraph ist das derzeit mächtigste Framework für die Entwicklung von zustandsbehafteten, Multi-Actor-Anwendungen mit LLMs. Es wurde von LangChain entwickelt und ermöglicht es Dir, komplexe Agenten-Workflows als gerichtete Graphen zu modellieren — mit Zuständen, bedingten Übergängen, Schleifen und paralleler Ausführung.
Wenn Du schon mal versucht hast, mit einfachen LangChain-Chains einen Multi-Agent-Workflow zu bauen, weißt Du, wie schnell es chaotisch wird. LangGraph löst genau dieses Problem: Statt spaghetticode-artige Prompt-Ketten zu bauen, definierst Du einen klaren Graphen mit Nodes (Agenten), Edges (Übergänge) und einem gemeinsamen State (Zustand). Das ist näher an echter Softwarearchitektur als an Prompt-Engineering.
In diesem Tutorial baue ich Dich von den Grundkonzepten bis zu einem vollständigen Multi-Agent-System mit Human-in-the-Loop, Memory und Fehlerbehandlung auf. Alle Code-Beispiele sind lauffähig.
TL;DR — LangGraph in 90 Sekunden
LangGraph ist ein Framework für zustandsbehaftete Multi-Agent-Anwendungen, das auf Graph-Theorie basiert: Agenten sind Nodes, Übergänge sind Edges, und ein gemeinsamer State wird zwischen allen Nodes geteilt.
---
Die 4 Kernkonzepte: State (geteilter Zustand), Nodes (Agenten-Funktionen), Edges (Übergänge), Conditional Edges (bedingte Routen).
Der größte Vorteil: Du kannst Schleifen (Cycles) bauen — ein Reviewer-Agent kann den Writer-Agenten zurücksenden, wenn die Qualität nicht reicht. Das ist mit einfachen Chains nicht möglich.
Die Lernkurve: Steiler als CrewAI, aber Du hast feingranulare Kontrolle über jeden Schritt. Wenn Du komplexe Workflows baust, ist LangGraph die beste Wahl.
Ende der kompakten Erklärung!
Was ist LangGraph — und warum brauchst Du es?
Das Problem mit einfachen Chains
Stell Dir vor, Du willst einen Content-Creation-Pipeline bauen: Ein Agent recherchiert, ein Agent schreibt, ein Agent reviewed. Mit einfachen LangChain-Chains sieht das so aus:
# Naiver Ansatz — funktioniert, aber nicht gut
research = llm.invoke("Recherchiere Thema X")
draft = llm.invoke(f"Schreibe Artikel: {research}")
final = llm.invoke(f"Verbessere: {draft}")
Das funktioniert für einfache Fälle. Aber was, wenn:
- Der Reviewer den Artikel ablehnt und der Writer überarbeiten soll? → Schleife nötig
- Du den State zwischen Agenten teilen willst (z.B. alle bisherigen Nachrichten)? → State Management nötig
- Du basierend auf dem Inhalt unterschiedlich routen willst? → Bedingte Übergänge nötig
- Du den Workflow anhalten und auf menschliche Bestätigung warten willst? → Human-in-the-Loop nötig
- Du mehrere Agenten parallel laufen lassen willst? → Parallele Ausführung nötig
Genau das löst LangGraph.
Die LangGraph-Architektur
LangGraph modelliert Agenten-Workflows als State Graphs — gerichtete Graphen, in denen:
- Nodes (Knoten) = Agenten-Funktionen, die den State empfangen, verarbeiten und zurückgeben
- Edges (Kanten) = Übergänge zwischen Nodes, die sequenziell oder bedingt sein können
- State = Ein geteiltes Dictionary (oder Pydantic-Model), das zwischen allen Nodes fließt
- Conditional Edges = Funktionen, die basierend auf dem State entscheiden, welche Node als nächstes ausgeführt wird
- Cycles = Schleifen, die eine Node mehrfach durchlaufen können (z.B. Writer → Reviewer → Writer)
Das Konzept ist inspiriert von Workflow-Engines wie Apache Airflow oder Temporal, aber speziell für LLM-basierte Agenten optimiert.
Warum Graphen und nicht einfach Ketten?
| Eigenschaft | Einfache Chain | LangGraph |
|---|---|---|
| Lineare Abfolge | ✅ | ✅ |
| Bedingte Verzweigungen | ❌ | ✅ |
| Schleifen (Cycles) | ❌ | ✅ |
| Geteilter State | Schwierig | ✅ Nativ |
| Parallele Ausführung | ❌ | ✅ |
| Human-in-the-Loop | ❌ | ✅ |
| Checkpointing / Memory | ❌ | ✅ |
| Graph-Visualisierung | ❌ | ✅ |
Installation und Setup
pip install langgraph langchain-openai langchain-core
Zusätzlich empfohlen für dieses Tutorial:
pip install langchain-anthropic # Für Claude-Modelle
pip install langgraph-checkpoint-sqlite # Für SQLite-Persistence
pip install grandalf # Für Graph-Visualisierung im Terminal
API-Keys setzen:
export OPENAI_API_KEY="sk-..."
# Optional für Claude:
export ANTHROPIC_API_KEY="sk-ant-..."
Wichtig: Verwende niemals hardcoded API-Keys im Code. Nutze immer Umgebungsvariablen oder .env-Dateien mit python-dotenv.
Die 4 Kernkonzepte — Im Detail
1. State — Das Herzstück
Der State ist ein geteiltes Datenobjekt, das zwischen allen Nodes im Graphen weitergegeben wird. Jede Node kann den State lesen und aktualisieren. Das ist der fundamentale Unterschied zu einfachen Chains, wo jeder Schritt nur die Ausgabe des vorherigen sieht.
State als TypedDict (empfohlen für einfache Fälle):
from typing import TypedDict, List, Optional
class ContentState(TypedDict):
topic: str # Das Thema, das bearbeitet wird
research: str # Rechercheergebnisse
draft: str # Aktueller Entwurf
final_article: str # Finaler Artikel
messages: List[str] # Vollständiger Nachrichtenverlauf
revision_count: int # Anzahl der Überarbeitungen
approved: bool # Wurde der Artikel genehmigt?
feedback: Optional[str] # Feedback des Reviewers
Warum TypedDict? Es gibt Dir Type-Safety und IDE-Autovervollständigung, ohne die Flexibilität eines Dictionaries zu verlieren. Für komplexere Anwendungen kannst Du auch Pydantic-Models verwenden:
from pydantic import BaseModel, Field
class ContentState(BaseModel):
topic: str = Field(description="Das zu bearbeitende Thema")
research: str = Field(default="", description="Rechercheergebnisse")
draft: str = Field(default="", description="Aktueller Entwurf")
revision_count: int = Field(default=0)
approved: bool = Field(default=False)
State-Reduction — Wie Updates funktionieren:
Standardmäßig überschreibt ein Node-Return den State. Aber Du kannst auch Reducer-Funktionen definieren, um State-Felder zu akkumulieren:
from typing import Annotated
from operator import add
class ContentState(TypedDict):
# messages wird appended, nicht überschrieben
messages: Annotated[List[str], add]
# research wird überschrieben (Standard)
research: str
Wenn jetzt zwei Nodes messages zurückgeben, werden die Listen kombiniert statt überschrieben. Das ist extrem nützlich für Nachrichtenverläufe.
2. Nodes — Die Agenten
Nodes sind Python-Funktionen, die den State empfangen, verarbeiten und einen aktualisierten State (oder ein Partial-Update) zurückgeben. Jede Node ist ein Agent oder eine Verarbeitungseinheit.
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", temperature=0)
def researcher(state: ContentState) -> dict:
"""Recherche-Agent: Sammelt Informationen zum Thema."""
topic = state["topic"]
prompt = f"""Du bist ein Recherche-Experte. Sammle die wichtigsten
Informationen zum Thema: {topic}.
Strukturiere Deine Antwort in:
1. Definition und Grundlagen
2. Aktuelle Entwicklungen
3. Praxisbeispiele
4. Häufige Missverständnisse
Gib nur die Recherche zurück, keine Einleitung."""
response = llm.invoke(prompt)
# Partial-Update: Nur veränderte Felder zurückgeben
return {
"research": response.content,
"messages": [f"Researcher: {response.content[:200]}..."]
}
Wichtige Punkte zu Nodes:
- Partial Updates: Du musst nicht den gesamten State zurückgeben, nur die Felder, die Du änderst. LangGraph merged das mit dem bestehenden State.
- Keine Side Effects: Nodes sollten idealerweise reine Funktionen sein (Input → Output). Side Effects (Datei-Schreiben, API-Calls) sollten explizit gehandhabt werden.
- Error Handling: Wenn eine Node eine Exception wirft, wird der gesamte Graph gestoppt. Nutze try/except für fehleranfällige Operationen.
def researcher(state: ContentState) -> dict:
try:
response = llm.invoke(prompt)
return {"research": response.content}
except Exception as e:
# Fehler in State schreiben statt Graph abzubrechen
return {
"research": f"Fehler bei Recherche: {str(e)}",
"messages": [f"Researcher ERROR: {str(e)}"]
}
3. Edges — Die Übergänge
Edges definieren, welche Node nach der aktuellen ausgeführt wird. Es gibt drei Arten:
Simple Edges (feste Übergänge):
# Nach researcher kommt immer writer
workflow.add_edge("researcher", "writer")
Conditional Edges (bedingte Übergänge): Das ist die mächtigste Funktion von LangGraph. Eine Routing-Funktion entscheidet basierend auf dem State, welche Node als nächstes kommt:
def route_after_review(state: ContentState) -> str:
"""Entscheidet nach dem Review, was passiert."""
if state.get("approved"):
return "publish" # Genehmigt → veröffentlichen
elif state.get("revision_count", 0) >= 3:
return "human_review" # Zu viele Revisionen → Mensch prüfen
else:
return "writer" # Nicht genehmigt → Writer überarbeitet
# Conditional Edge hinzufügen
workflow.add_conditional_edges(
"reviewer", # Source-Node
route_after_review, # Routing-Funktion
{
"publish": "publisher",
"human_review": "human_node",
"writer": "writer"
}
)
Entry Point (Start-Node):
workflow.set_entry_point("researcher")
4. Cycles — Schleifen für iterative Verbesserung
Cycles sind der wichtigste Unterschied zwischen LangGraph und einfachen Chains. Ein Cycle ermöglicht es einem Agenten-Team, iterativ zu arbeiten:
researcher → writer → reviewer → (nicht gut?) → writer → reviewer → (gut!) → publish
In LangGraph erreichst Du das mit Conditional Edges, die zurück zu einer früheren Node führen:
# Reviewer kann Writer zurücksenden (Cycle!)
workflow.add_conditional_edges(
"reviewer",
route_after_review, # Kann "writer" zurückgeben
)
# Writer geht immer zum Reviewer
workflow.add_edge("writer", "reviewer")
Achtung — Endlosschleifen vermeiden: Implementiere immer einen Zähler oder eine maximale Anzahl von Iterationen:
def route_after_review(state: ContentState) -> str:
revision_count = state.get("revision_count", 0)
if state.get("approved"):
return "publish"
if revision_count >= 5:
return "human_review" # Notbremse
return "writer" # Nochmal versuchen
Ein vollständiges Beispiel — Schritt für Schritt
Wir bauen jetzt ein vollständiges Multi-Agent-System: Researcher + Writer + Reviewer + Publisher mit Human-in-the-Loop, Revision-Limit und Memory.
Schritt 1: State definieren
from typing import TypedDict, List, Optional, Annotated
from operator import add
class ContentState(TypedDict):
topic: str
research: str
draft: str
final_article: str
messages: Annotated[List[str], add] # Wird appended, nicht überschrieben
revision_count: int
approved: bool
feedback: Optional[str]
Schritt 2: Nodes (Agenten) definieren
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o", temperature=0.7) # Etwas Kreativität für Writer
def researcher(state: ContentState) -> dict:
"""Recherche-Agent: Sammelt strukturierte Informationen."""
topic = state["topic"]
prompt = f"""Du bist ein Recherche-Experte. Sammle die wichtigsten
Informationen zum Thema: {topic}.
Berücksichtige:
- Definition und Grundlagen
- Aktuelle Entwicklungen (2025-2026)
- Praxisbeispiele und Use Cases
- Häufige Missverständnisse und Fallstricke
Gib nur die strukturierte Recherche zurück."""
response = llm.invoke(prompt)
return {
"research": response.content,
"messages": [f"Researcher: Recherche abgeschlossen ({len(response.content)} Zeichen)"]
}
def writer(state: ContentState) -> dict:
"""Writer-Agent: Schreibt Artikel basierend auf Recherche und Feedback."""
research = state["research"]
feedback = state.get("feedback", "")
revision_count = state.get("revision_count", 0)
if feedback and revision_count > 0:
prompt = f"""Du bist ein professioneller Autor. Überarbeite den
folgenden Artikel basierend auf dem Feedback des Reviewers.
Recherche: {research}
Bisheriger Entwurf: {state.get('draft', '')}
Feedback des Reviewers: {feedback}
Schreibe den verbesserten Artikel. Berücksichtige alle Punkte
aus dem Feedback."""
else:
prompt = f"""Du bist ein professioneller Autor. Schreibe einen
umfassenden, gut strukturierten Artikel zum Thema: {state['topic']}
Basierend auf dieser Recherche: {research}
Der Artikel soll:
- Eine klare Einleitung haben
- Zwischenüberschriften verwenden
- Praxisbeispiele enthalten
- Eine Zusammenfassung am Ende haben"""
response = llm.invoke(prompt)
return {
"draft": response.content,
"revision_count": revision_count + 1,
"messages": [f"Writer: Entwurf v{revision_count + 1} erstellt"]
}
def reviewer(state: ContentState) -> dict:
"""Reviewer-Agent: Prüft Qualität und gibt Feedback."""
draft = state["draft"]
revision_count = state.get("revision_count", 0)
prompt = f"""Du bist ein strenger Reviewer. Bewerte den folgenden Artikel
nach diesen Kriterien:
1. Struktur und Lesbarkeit (1-10)
2. Fachliche Korrektheit (1-10)
3. Praxisbezug (1-10)
4. Vollständigkeit (1-10)
Artikel: {draft}
Wenn alle Kriterien mindestens 7/10 erreichen, antworte mit "APPROVED".
Sonst antworte mit "REJECTED" und gib konkretes Feedback zur Verbesserung."""
response = llm.invoke(prompt)
content = response.content
approved = "APPROVED" in content.upper()
return {
"approved": approved,
"feedback": content if not approved else None,
"final_article": draft if approved else "",
"messages": [f"Reviewer: {'Genehmigt' if approved else 'Abgelehnt (Revision ' + str(revision_count) + ')'}"]
}
def publisher(state: ContentState) -> dict:
"""Publisher-Agent: Formatiert den finalen Artikel."""
article = state["final_article"]
prompt = f"""Formatiere diesen Artikel als Markdown mit:
- Titel als H1
- Metadaten (Autor, Datum, Lesezeit)
- Saubere Abschnittsüberschriften
Artikel: {article}"""
response = llm.invoke(prompt)
return {
"final_article": response.content,
"messages": [f"Publisher: Artikel veröffentlicht"]
}
def human_review(state: ContentState) -> dict:
"""Human-in-the-Loop: Mensch entscheidet bei zu vielen Revisionen."""
print(f"\n=== HUMAN REVIEW ===")
print(f"Thema: {state['topic']}")
print(f"Revisionen: {state.get('revision_count', 0)}")
print(f"Feedback des Reviewers: {state.get('feedback', 'Keines')}")
print(f"\nLetzter Entwurf:\n{state.get('draft', '')[:500]}...")
approval = input("\nArtikel genehmigen? (y/n): ")
return {
"approved": approval.lower() == "y",
"messages": [f"Human: {'Genehmigt' if approval.lower() == 'y' else 'Abgelehnt'}"]
}
Schritt 3: Routing-Funktionen definieren
from langgraph.graph import END
def route_after_review(state: ContentState) -> str:
"""Routing nach dem Review mit Notbremse."""
if state.get("approved"):
return "publisher"
if state.get("revision_count", 0) >= 3:
return "human_review"
return "writer" # Zurück zum Writer
def route_after_human(state: ContentState) -> str:
"""Routing nach menschlicher Prüfung."""
if state.get("approved"):
return "publisher"
return END # Abbrechen, wenn Mensch ablehnt
Schritt 4: Graph zusammenbauen
from langgraph.graph import StateGraph
# Graph erstellen
workflow = StateGraph(ContentState)
# Alle Nodes hinzufügen
workflow.add_node("researcher", researcher)
workflow.add_node("writer", writer)
workflow.add_node("reviewer", reviewer)
workflow.add_node("publisher", publisher)
workflow.add_node("human_review", human_review)
# Edges definieren
workflow.set_entry_point("researcher")
# researcher → writer (immer)
workflow.add_edge("researcher", "writer")
# writer → reviewer (immer)
workflow.add_edge("writer", "reviewer")
# reviewer → conditional (writer, publisher, oder human_review)
workflow.add_conditional_edges(
"reviewer",
route_after_review,
{
"writer": "writer",
"publisher": "publisher",
"human_review": "human_review"
}
)
# human_review → conditional (publisher oder END)
workflow.add_conditional_edges(
"human_review",
route_after_human,
{
"publisher": "publisher",
END: END
}
)
# publisher → END (immer)
workflow.add_edge("publisher", END)
# Graph kompilieren
app = workflow.compile()
Schritt 5: Ausführen
# Initialer State
initial_state = {
"topic": "KI-Programmierung: Best Practices 2026",
"research": "",
"draft": "",
"final_article": "",
"messages": [],
"revision_count": 0,
"approved": False,
"feedback": None
}
# Graph ausführen
result = app.invoke(initial_state)
# Ergebnis anzeigen
print("\n" + "=" * 60)
print("FINAL ARTICLE:")
print("=" * 60)
print(result["final_article"])
print("\n" + "=" * 60)
print(f"Revisionen: {result['revision_count']}")
print(f"Nachrichtenverlauf:")
for msg in result["messages"]:
print(f" - {msg}")
Was passiert hier?
- Researcher sammelt Informationen zum Thema
- Writer schreibt ersten Entwurf basierend auf der Recherche
- Reviewer bewertet den Entwurf nach 4 Kriterien
- Wenn nicht genehmigt: Zurück zum Writer mit Feedback (Cycle!)
- Nach 3 gescheiterten Revisionen: Human Review (Notbremse)
- Wenn genehmigt: Publisher formatiert den finalen Artikel
- END — Artikel ist fertig
Der gesamte Workflow ist ein Graph mit einem Cycle (writer ↔ reviewer) und zwei Conditional Edges (reviewer und human_review).
Fortgeschrittene Patterns
Memory und Persistence mit Checkpointing
LangGraph kann den State nach jeder Node-Ausführung speichern (Checkpointing). Das ermöglicht:
- Wiederherstellung nach Abstürzen
- Fortsetzung von pausierten Workflows
- Konversationsverläufe über mehrere Sessions
from langgraph.checkpoint.memory import MemorySaver
# Für Persistenz über Neustarts hinweg:
# from langgraph.checkpoint.sqlite import SqliteSaver
# In-Memory Checkpointer
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
# Mit Thread-ID ausführen (wichtig für Memory!)
result = app.invoke(
initial_state,
config={"configurable": {"thread_id": "article-ki-programmierung"}}
)
# Später fortsetzen (z.B. nach Human-Approval)
result2 = app.invoke(
{"approved": True}, # Nur das Update schicken
config={"configurable": {"thread_id": "article-ki-programmierung"}}
)
Wie es funktioniert: Der Checkpointer speichert den State nach jeder Node-Ausführung. Mit der thread_id kannst Du später denselben Thread fortsetzen. Das ist besonders nützlich für Human-in-the-Loop, wo der Workflow pausiert wird, bis ein Mensch entscheidet.
Parallele Ausführung
Du kannst mehrere Nodes parallel ausführen und dann zusammenführen:
def researcher_general(state: ContentState) -> dict:
"""Recherchiert allgemeine Informationen."""
response = llm.invoke(f"Allgemeine Infos zu: {state['topic']}")
return {"research_general": response.content}
def researcher_technical(state: ContentState) -> dict:
"""Recherchiert technische Details."""
response = llm.invoke(f"Technische Details zu: {state['topic']}")
return {"research_technical": response.content}
def merge_research(state: ContentState) -> dict:
"""Führt beide Recherchen zusammen."""
combined = f"Allgemein:\n{state['research_general']}\n\nTechnisch:\n{state['research_technical']}"
return {"research": combined}
# Im Graph:
workflow.add_node("researcher_general", researcher_general)
workflow.add_node("researcher_technical", researcher_technical)
workflow.add_node("merge", merge_research)
# Beide Researcher parallel vom Start
workflow.set_entry_point("researcher_general")
workflow.add_edge("researcher_general", "researcher_technical")
# Eigentlich parallel: Beide vom Entry, beide → merge
# LangGraph führt parallele Nodes automatisch zusammen
Tool-Integration — Agenten mit Werkzeugen
Agenten werden erst mächtig, wenn sie Tools nutzen können. LangGraph integriert nahtlos LangChain-Tools:
from langchain.tools import Tool
from langchain_community.tools import DuckDuckGoSearchRun
# Web-Search-Tool
search = DuckDuckGoSearchRun()
def researcher_with_tools(state: ContentState) -> dict:
"""Recherche-Agent mit Web-Search."""
topic = state["topic"]
# Erst suchen
search_results = search.run(f"{topic} 2026 latest developments")
# Dann LLM mit Suchergebnissen füttern
prompt = f"""Basierend auf diesen Suchergebnissen, erstelle eine
strukturierte Recherche zum Thema {topic}:
Suchergebnisse: {search_results}"""
response = llm.invoke(prompt)
return {"research": response.content}
Sub-Graphs — Graphen in Graphen
Du kannst einen LangGraph als Node in einem anderen LangGraph verwenden. Das ist nützlich für modulare Architekturen:
# Sub-Graph für Recherche
research_graph = StateGraph(ContentState)
research_graph.add_node("web_search", web_searcher)
research_graph.add_node("summarize", summarizer)
research_graph.set_entry_point("web_search")
research_graph.add_edge("web_search", "summarize")
research_graph.add_edge("summarize", END)
research_app = research_graph.compile()
# Haupt-Graph nutzt Sub-Graph als Node
main_workflow = StateGraph(ContentState)
main_workflow.add_node("research", research_app) # Sub-Graph als Node!
main_workflow.add_node("write", writer)
main_workflow.set_entry_point("research")
main_workflow.add_edge("research", "write")
main_workflow.add_edge("write", END)
Streaming — Ergebnisse in Echtzeit
LangGraph unterstützt Streaming, sodass Du Zwischenergebnisse siehst, während der Graph läuft:
# Stream alle Node-Updates
for event in app.stream(initial_state):
for node_name, node_output in event.items():
print(f"[{node_name}] → {list(node_output.keys())} updated")
# Stream nur die Messages
for event in app.stream(
initial_state,
stream_mode="values"
):
messages = event.get("messages", [])
if messages:
print(f"Latest: {messages[-1]}")
Graph visualisieren
LangGraph kann den Graphen visualisieren — extrem nützlich für Debugging:
# Im Jupyter Notebook:
from IPython.display import Image, display
# Graph als Bild rendern
display(Image(app.get_graph().draw_mermaid_png()))
# Im Terminal:
app.get_graph().print_ascii()
Das gibt Dir eine visuelle Darstellung aller Nodes, Edges und Conditional Routes — unverzichtbar bei komplexen Graphen.
LangGraph vs. CrewAI — Welches Framework für welchen Anwendungsfall?
| Aspekt | LangGraph | CrewAI |
|---|---|---|
| Paradigma | State Graph (Graph-Theorie) | Role-Based Agents (Team-Metapher) |
| Flexibilität | Sehr hoch — jeder Schritt kontrollierbar | Mittel — Abstraktion verbirgt Details |
| Lernkurve | Steil — Graph-Konzepte müssen verstanden werden | Flach — “Definiere Agenten, starte Task” |
| Kontrolle | Feingranular — Conditional Edges, State-Management | Abstrahiert — Framework entscheidet viel selbst |
| Debugging | Graph-Visualisierung, Streaming, Tracing mit LangSmith | Logs, einfache Print-Statements |
| Cycles/Schleifen | Nativ unterstützt | Eingeschränkt (max_iterations) |
| Memory/State | Nativ (Checkpointing, State-Reduction) | Eingeschränkt (Memory-Parameter) |
| Human-in-the-Loop | Nativ (interrupt_before, interrupt_after) | Manuell implementiert |
| Eignung | Komplexe Workflows, Produktions-Systeme | Schnelle Prototypen, einfache Agenten-Teams |
| Community | LangChain-Ökosystem (sehr groß) | Wachsend, aber kleiner |
Meine Empfehlung:
- CrewAI für Prototypen und einfache Agenten-Teams (2-3 Agenten, lineare Abläufe)
- LangGraph für Produktions-Systeme, komplexe Workflows mit Schleifen, Bedingungen und Human-in-the-Loop
- Beide für unterschiedliche Projekte — sie schließen sich nicht gegenseitig aus
Best Practices aus der Praxis
1. State so klein wie möglich halten
Nicht jede Information muss in den State. Lokale Variablen in Nodes sind oft ausreichend. Der State sollte nur Daten enthalten, die wirklich zwischen Nodes geteilt werden müssen.
2. Revision-Limits immer setzen
Ohne Limit kann ein Cycle endlos laufen. Setze immer einen Zähler und eine Notbremse:
MAX_REVISIONS = 5
def route_after_review(state: ContentState) -> str:
if state.get("revision_count", 0) >= MAX_REVISIONS:
return "human_review" # oder END
# ...
3. Tracing mit LangSmith aktivieren
LangSmith (von LangChain) gibt Dir detaillierte Einblicke in jeden Schritt:
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "ls-..."
Du siehst dann jeden LLM-Call, jeden State-Übergang und jede Token-Berechnung im LangSmith-Dashboard.
4. Kosten im Auge behalten
Jeder Node macht mindestens einen LLM-Call. Bei 3 Agenten und 3 Revisionen sind das 9+ LLM-Calls. Mit GPT-4o können das schnell 5-10$ pro Durchlauf sein. Nutze günstigere Modelle für einfache Tasks (z.B. GPT-4o-mini für den Reviewer).
5. Lokale Modelle für Entwicklung
Für die Entwicklung und Tests nutze lokale Modelle (Ollama), um Kosten zu sparen:
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(model="llama3.1:8b", temperature=0)
Häufige Probleme und Lösungen
Problem: “Graph doesn’t have an entry point”
Lösung: Du musst workflow.set_entry_point("node_name") vor dem Kompilieren aufrufen.
Problem: “Node ‘xyz’ not found”
Lösung: Jede Node, die in Edges referenziert wird, muss mit add_node hinzugefügt werden, bevor Edges definiert werden.
Problem: Endlosschleife
Lösung: Conditional Edge gibt immer dieselbe Node zurück. Prüfe die Routing-Funktion — sie muss basierend auf dem State unterschiedliche Werte zurückgeben.
Problem: State wird nicht aktualisiert
Lösung: Node gibt Partial-Update zurück, aber Feld nutzt Annotated-Reducer. Prüfe, ob der Reducer korrekt definiert ist. Bei Annotated[List[str], add] muss die Node eine Liste zurückgeben, die addiert wird.
Prüfungsrelevante Punkte
- LangGraph: Framework für zustandsbehaftete Multi-Agent-Anwendungen, basierend auf Graph-Theorie
- 4 Kernkonzepte: State (geteilter Zustand), Nodes (Agenten-Funktionen), Edges (Übergänge), Conditional Edges (bedingte Routen)
- Cycles: Schleifen ermöglichen iterative Verbesserung (Writer ↔ Reviewer)
- State-Reduction: Annotated-Reducer (z.B.
add) für akkumulierende State-Felder - Human-in-the-Loop: Workflow pausieren für menschliche Entscheidungen
- Checkpointing: State nach jeder Node speichern, Wiederherstellung nach Absturz
- Parallele Ausführung: Mehrere Nodes gleichzeitig, automatisches Zusammenführen
- Sub-Graphs: Graph als Node in anderem Graph — modulare Architektur
- Streaming: Echtzeit-Updates während der Graph-Ausführung
- LangSmith: Tracing und Debugging für LangGraph-Workflows
- Vergleich: LangGraph (flexibel, komplex, Produktionsreif) vs. CrewAI (einfach, schnell, Prototypen)
FAQ
Braucht man LangChain-Kenntnisse für LangGraph? Ja, grundlegende LangChain-Kenntnisse sind hilfreich, da LangGraph auf LangChain aufbaut (gleiche LLM-Integration, gleiche Tools, gleiche Runnables). Wenn Du LangChain noch nicht kennst, solltest Du zuerst die Grundlagen von LangChain lernen.
Ist LangGraph kostenlos? LangGraph selbst ist Open Source (MIT-Lizenz). Die LLM-Kosten (OpenAI, Anthropic) fallen zusätzlich an. LangSmith (Tracing) hat eine kostenlose Stufe, ist aber für intensiven Gebrauch kostenpflichtig.
Kann LangGraph mit lokalen Modellen arbeiten? Ja, über Ollama, llama.cpp oder LM Studio. Das ist besonders für Entwicklung und Tests empfehlenswert, um API-Kosten zu sparen. Für Produktion sind Cloud-Modelle meist qualitativ besser.
Wie debugge ich einen LangGraph-Workflow?
Drei Tools: 1) app.get_graph().draw_mermaid_png() für visuelle Graph-Darstellung. 2) Streaming mit app.stream() für Echtzeit-Updates. 3) LangSmith für detailliertes Tracing jedes LLM-Calls.
Was ist der Unterschied zwischen LangGraph und LangChain? LangChain ist ein allgemeines Framework für LLM-Anwendungen (Chains, Agents, Tools). LangGraph ist spezialisiert auf zustandsbehaftete, Multi-Actor-Workflows mit Graph-Struktur. LangGraph baut auf LangChain auf, ersetzt es aber nicht.
Kann ich LangGraph in Produktion einsetzen? Ja, LangGraph ist für Produktion geeignet. Mit Checkpointing (Persistence), Error Handling und LangSmith-Tracing hast Du alles, was Du für zuverlässige Produktions-Systeme brauchst. LangGraph Cloud bietet zudem Managed-Hosting für LangGraph-Anwendungen.
Empfohlene Literatur
KI-Agenten
Bücher über KI-Agenten, Multiagentensysteme und Agent-Orchestrierung
The AI Agent Handbook
Bei Amazon ansehenAffiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.
Multi-Agent Systems: A Modern Approach
Bei Amazon ansehenAffiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.
Coding mit KI: Das Praxisbuch für die Softwareentwicklung
Bei Amazon ansehenAffiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.





