Frag ein Team, warum es von LangChain zu LangGraph gewechselt hat, und du bekommst meistens ein Achselzucken und „Der Agent hat ständig komische Sachen gemacht." Diese Antwort ist akkurater als sie klingt, verbirgt aber den eigentlichen Mechanismus. Ein AgentExecutor-Chain ist eine Schleife im Trenchcoat – er sieht aus, als würde er Entscheidungen treffen, aber unter der Haube ist es eine fixe While-Schleife, die ein Modell aufruft, nach einem Tool-Call prüft, das Tool ausführt und wiederholt, bis das Modell aufhört, Tools anzufordern. Es gibt keine Möglichkeit, ihn anzuhalten, keine Möglichkeit, ihn zu verzweigen, und keine Möglichkeit, ihn etwas über das erinnern zu lassen, was in den nächsten Prompt-Iteration passt.
Das ist für eine große Anzahl echter Anwendungsfälle völlig in Ordnung, und das ist der Teil, den die meisten „LangGraph ist besser"-Meinungen überspringen. Die eigentliche Frage lautet nicht „Welches ist besser?" – sondern „Muss mein Control-Flow auf sich selbst zurückschleifen, in parallele Zweige aufgabeln oder einen Neustart überleben?" Die meisten Agents tun das nicht. Einige tun es tatsächlich, und sie in eine lineare Chain zu zwingen, schlägt nicht laut fehl – sie schlägt als flakiges Verhalten, stiller State-Verlust und Retries fehl, die still Side-Effects erneut ausführen. Dieser Post handelt davon, zu wissen, in welcher Kategorie du bist, bevor du eine Zeile Orchestrierungs-Code schreibst.
Du lernst:
- Was eine LangChain-Chain und
AgentExecutorunter der Haube wirklich tun - Die drei konkreten Symptome, die bedeuten, dass du eine Chain überlebt hast
- Wie LangGraph Control-Flow als Graph mit Nodes, Edges und gemeinsamem State modelliert
- Einen Direktvergleich von
AgentExecutorund einemStateGraphfür dieselbe Aufgabe - Wie LangGraphs Checkpointer dir dauerhaften, resumierbaren State gibt, den eine Chain nicht hat
- Ein Entscheidungsframework für die Wahl zwischen beiden in deinem nächsten Projekt
- Was das Migrieren eines bestehenden
AgentExecutor-Agents zu LangGraph tatsächlich beinhaltet
Inhaltsverzeichnis
- Was LangChain eigentlich ist
- Wo eine Chain nicht mehr ausreicht
- Was LangGraph hinzufügt
- AgentExecutor vs. StateGraph: Derselbe Agent, zwei Wege
- Dauerhafter State und warum er wichtig ist
- Wann LangGraph verwenden – und wann nicht
- Migration von AgentExecutor zu LangGraph
- Wie es weitergeht
Was LangChain eigentlich ist
Die Kernabstraktion von LangChain ist die Chain: eine Sequenz von Schritten – ein Prompt-Template, ein Modell-Aufruf, ein Output-Parser – zusammengesetzt mit dem |-Operator über LCEL (LangChain Expression Language). Jeder Schritt nimmt den Output des vorherigen Schritts als Input. Es ist eine Pipeline, und Pipelines sind ein wirklich gutes Modell für alles, was tatsächlich linear ist: Dokumente abrufen, in einen Prompt stecken, das Modell aufrufen, die Antwort parsen.
from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_template("Summarize this in two sentences:\n\n{text}")
model = ChatAnthropic(model="claude-sonnet-5")
chain = prompt | model | StrOutputParser()
result = chain.invoke({"text": long_document})
Darauf aufgebaute Agents verwenden AgentExecutor, der dieselbe Idee in einer Schleife verpackt: Rufe das Modell mit einem Satz von an es gebundenen Tools auf, prüfe, ob die Antwort nach einem Tool-Call fragt, führe das Tool aus, wenn ja, gib das Ergebnis zurück, und wiederhole, bis das Modell eine endgültige Antwort statt eines Tool-Calls zurückgibt.
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful research assistant."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(model, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, max_iterations=10)
result = executor.invoke({"input": "What's the current weather in Lahore, and is it good for a run?"})
Das funktioniert gut für eine Single-Agent-, Single-Thread-Schleife mit einer begrenzten Anzahl von Tool-Calls. Die Schleife ist jedoch undurchsichtig – du kontrollierst nicht, was zwischen Iterationen passiert, du kannst sie nicht mitten in der Schleife anhalten und später fortsetzen, und es gibt keine Möglichkeit, zwei Reasoning-Zweige parallel laufen zu lassen und sie dann wieder zusammenzuführen. max_iterations ist das einzige echte Sicherheitsventil, und es ist ein stumpfes: Es stoppt die Schleife einfach, leitet sie aber nicht um.
Wo eine Chain nicht mehr ausreicht
Drei Symptome tauchen zuverlässig auf, kurz bevor ein Team nach LangGraph greift. Keines davon ist exotisch – sie sind das, was natürlich passiert, wenn die Aufgabe eines Agents etwas realer wird.
1. Der Agent muss mit einem Zweck auf sich selbst zurückschleifen, nicht nur wiederholen. Eine Chain wiederholt durch erneutes Ausführen derselben fixen Sequenz. Aber ein wirklich nützliches Muster – Code schreiben, Tests ausführen, den Fehler lesen, den Code fixen, Tests erneut ausführen – erfordert, dass das Ergebnis eines Schritts bestimmt, welcher Schritt als nächstes läuft, potenziell mehrmals zyklisch, bevor er endet. AgentExecutor kann das technisch durch wiederholte Tool-Calls erreichen, aber du hast weder Sichtbarkeit noch Kontrolle über den Zyklus selbst; es ist nur „die Schleife hat zufällig das Tool erneut aufgerufen."
2. Der Agent muss verzweigen und möglicherweise Schritte parallel ausführen. Angenommen, eine Anfrage soll entweder zu einem schnellen Antwortpfad oder zu einem mehrstufigen Recherchepfad gehen, entschieden durch vorherige Klassifizierung der Abfrage. Eine Chain ist ein Pfad. Eine Verzweigung in AgentExecutor zu bekommen bedeutet, benutzerdefinierte Logik außerhalb des Executors zu schreiben und ihn bedingt erneut aufzurufen – an diesem Punkt baust du deine eigene Orchestrierung auf einer Abstraktion, die nicht dafür konzipiert war, sie preiszugeben.
3. Der State des Agents muss über den Prozess hinaus überleben, der ihn ausführt. Wenn der Prozess einer Chain abstürzt, neu startet oder eine Anfrage für Minuten (eine menschliche Überprüfung) oder Tage (Warten auf ein externes Ereignis) pausiert werden muss, hat AgentExecutor nirgends, wo er diesen State ablegen könnte. Er lebt im Speicher, in Python-Variablen, für die Dauer eines einzigen .invoke()-Aufrufs. Wenn dieser Aufruf endet, endet auch alles, was er wusste.
Wenn keines dieser drei auf das zutrifft, was du baust, ist eine Chain keine mindere Wahl – sie ist normalerweise die richtige Wahl, weil sie einfacher zu verstehen ist, günstiger auszuführen (kein Graph-Traversal-Overhead) und mit einem linearen Stack-Trace leichter zu debuggen.
Was LangGraph hinzufügt
LangGraph rahmt den Control-Flow eines Agents als expliziten Graphen: eine Menge von Nodes (jede eine Funktion, die den aktuellen State empfängt und Updates dafür zurückgibt), Edges (die Nodes verbinden, entweder bedingungslos oder basierend auf einer Routing-Funktion) und ein geteiltes State-Objekt, das jeder Node liest und schreibt. Zyklen sind hier kein Workaround – sie sind der Standardfall, den ein Graph natürlich unterstützt, weil eine Edge zurück auf einen bereits besuchten Node zeigen kann.
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
def call_model(state: AgentState):
response = model_with_tools.invoke(state["messages"])
return {"messages": [response]}
def should_continue(state: AgentState):
last_message = state["messages"][-1]
return "tools" if last_message.tool_calls else END
builder = StateGraph(AgentState)
builder.add_node("agent", call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
builder.add_edge("tools", "agent")
graph = builder.compile()
Das sieht ausführlicher aus als AgentExecutor für dieselbe einfache Schleife, und das ist es – das ist der Kompromiss. Was du dafür bekommst, ist, dass der Zyklus (agent → tools → agent) jetzt eine Tatsache ist, die du sehen, ändern und über die du nachdenken kannst, statt etwas, das in der internen Schleife einer Bibliothek vergraben ist. Du kannst einen dritten Node hinzufügen, der nur den State loggt, eine bedingte Edge einfügen, die zu einem Human-Review-Node statt automatisch zurückschleifen leitet, oder zwei unabhängige Zweige vom gleichen Node aus ausführen und ihre Ergebnisse zusammenführen – nichts davon passt natürlich in das Modell von AgentExecutor.
Der Vergleich, den Leute meinen, wenn sie „LangGraph vs. AgentExecutor" sagen, ist dieser: AgentExecutor gibt dir eine feste Schleifenform mit eingebautem Tool-Calling; StateGraph gibt dir eine beliebige Graphenform, bei der Tool-Calling nur eines der häufigen Muster ist, das du darauf aufbaust – keine Form, die dir das Framework kostenlos übergibt.
AgentExecutor vs. StateGraph: Derselbe Agent, zwei Wege
Um den Unterschied konkret zu machen, hier derselbe „recherchieren und zusammenfassen"-Agent: einmal als AgentExecutor, einmal als StateGraph mit einem expliziten Review-Zweig.
AgentExecutor-Version – linear, kein Zweig:
executor = AgentExecutor(agent=agent, tools=[search_tool, summarize_tool])
result = executor.invoke({"input": "Research recent LLM pricing changes and summarize."})
Jeder Lauf folgt demselben impliziten Pfad: Modell entscheidet, Tool läuft, Modell entscheidet erneut. Wenn du wolltest „Zusammenfassen nur, wenn die Recherche mehr als drei Quellen ergab, sonst eine Nachfrage stellen", müsstest du den Output des Executors nachverarbeiten und ihn möglicherweise erneut aufrufen – Glue-Code, der außerhalb des Frameworks sitzt.
StateGraph-Version – dieselbe Aufgabe, mit einem expliziten bedingten Zweig:
def route_after_research(state: AgentState) -> str:
sources = state.get("source_count", 0)
return "summarize" if sources > 3 else "ask_followup"
builder = StateGraph(AgentState)
builder.add_node("research", research_node)
builder.add_node("summarize", summarize_node)
builder.add_node("ask_followup", followup_node)
builder.add_edge(START, "research")
builder.add_conditional_edges("research", route_after_research)
builder.add_edge("summarize", END)
builder.add_edge("ask_followup", END)
graph = builder.compile()
Der Zweig ist jetzt ein erstklassiger Teil der Graph-Definition – sichtbar, isoliert testbar (du kannst route_after_research als reine Funktion unit-testen) und unabhängig davon, wie viele Tool-Calls der Recherche-Schritt intern benötigte. Das ist das eigentliche Delta: nicht „LangGraph ist klüger", sondern „die Control-Flow-Entscheidung ist von implizit (innerhalb der Schleife) zu explizit (ein Node im Graphen) gewechselt."
Parallele Zweige: Etwas, das AgentExecutor gar nicht kann
Das Recherchieren/Zusammenfassen-Beispiel oben ist ein bedingter Zweig – ein Pfad oder der andere. LangGraph unterstützt auch echtes Fan-out/Fan-in: mehrere Nodes, die vom gleichen Vorgänger aus laufen, wobei der Graph darauf wartet, dass alle abgeschlossen sind, bevor er weiterläuft. Das hat kein echtes Äquivalent in AgentExecutor, dessen Schleife inhärent single-threaded durch das Modell ist.
def route_to_all_sources(state: AgentState) -> list[str]:
return ["search_web", "search_docs", "search_internal_db"]
builder.add_conditional_edges("plan", route_to_all_sources)
builder.add_edge("search_web", "merge_results")
builder.add_edge("search_docs", "merge_results")
builder.add_edge("search_internal_db", "merge_results")
builder.add_node("merge_results", merge_node)
Jeder der drei Such-Nodes läuft unabhängig und schreibt seine Ergebnisse in den geteilten State; merge_results wird erst ausgeführt, wenn alle drei abgeschlossen sind, und sieht den kombinierten State. Das innerhalb von AgentExecutor zu replizieren bedeutet, das Framework völlig zu verlassen – eigenes asyncio.gather() über separate Executor-Instanzen auszuführen und die Ergebnisse manuell zusammenzufügen, was genau der Art von Orchestrierungs-Code ist, den LangGraph absorbieren soll.
Ein schneller Überblick
AgentExecutor | LangGraph StateGraph | |
|---|---|---|
| Control-Flow-Form | Feste Schleife (Modell aufrufen, Tool ausführen, wiederholen) | Beliebiger Graph, den du definierst |
| Verzweigung | Nicht nativ – erfordert externen Glue-Code | add_conditional_edges, erstklassig |
| Paralleles Fan-out | Nicht unterstützt | Nativ, über mehrere Edges von einem Node |
| Zyklen | Implizit innerhalb der Schleife, undurchsichtig | Explizite Edges, vollständig sichtbar |
| Persistierter State | Keiner – lebt im Speicher für ein .invoke() | Checkpointer (Memory, SQLite, Postgres) |
| Neustart nach einem Absturz | Nicht möglich | thread_id + Checkpointer |
| Debuggen | Stack-Trace durch Bibliotheks-Interna | State an jeder Node-Grenze inspizieren |
| Lernkurve | Niedriger – ein paar Funktionsaufrufe | Höher – du definierst Nodes, Edges und State explizit |
Dauerhafter State und warum er wichtig ist
Das andere Stück, das AgentExecutor wirklich fehlt, ist ein Checkpointer. LangGraph kann das vollständige State-Objekt nach jeder Node-Ausführung persistieren, indiziert nach einer thread_id, mit einem pluggable Backend – im Speicher für die Entwicklung, Postgres oder SQLite für alles, was einen Prozessneustart überleben muss.
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "user-42-session-1"}}
graph.invoke({"messages": [("user", "Start the research task")]}, config)
Da der Output jedes Nodes checkpointed wird, kannst du graph.invoke() später erneut mit derselben thread_id aufrufen und es nimmt genau dort auf, wo es aufgehört hat – dieselbe Message-History, derselbe Zwischenzustand, ohne erneutes Ausführen von Side-Effects, die bereits stattgefunden haben. AgentExecutor hat kein Äquivalent: Beende den Python-Prozess mitten in einem Lauf, und der gesamte Arbeitsspeicher des Agents ist weg. Diese Dauerhaftigkeit ist auch das, was Patterns wie Human-in-the-Loop-Review praktisch statt theoretisch macht – ein Graph kann an einem Node pausieren, unbegrenzt warten und mit dem State genau so fortfahren, wie er ihn verlassen hat. Das ist das Thema eines eigenen Deep-Dives, unten verlinkt.
Wann LangGraph verwenden – und wann nicht
Verwende eine einfache LangChain-Chain (mit oder ohne AgentExecutor) wenn:
- Die Aufgabe grundlegend linear ist: abrufen, anreichern, generieren.
- Tool-Calls begrenzt sind und keine bedingte Verzweigung basierend auf ihren Ergebnissen benötigen.
- Die gesamte Interaktion innerhalb einer einzigen Prozesslebensdauer abgeschlossen wird – kein Pausieren über Neustarts hinweg oder Warten auf einen Menschen über einen längeren Zeitraum.
- Du den kleineren, besser vorhersehbaren Oberfläche einer einfacheren Abstraktion schätzt.
Greife zu LangGraph, wenn mindestens eines davon zutrifft:
- Der Agent braucht einen Zyklus, dessen Exit-Bedingung vom ausgewerteten State abhängt, nicht nur „das Modell hat aufgehört, Tools aufzurufen" (Test-Fix-Retest-Schleifen, Multi-Round-Verhandlung zwischen Sub-Agents).
- Du brauchst explizite Verzweigung – verschiedene Pfade für verschiedene Klassifizierungen desselben Inputs, oder parallele Zweige, die aufgabeln und später wieder zusammenführen.
- Der Workflow muss einen Prozessneustart überleben oder für eine unbegrenzte Zeit pausiert werden müssen (menschliche Genehmigung, Warten auf einen async Webhook) und später mit vollständigem Kontext fortgesetzt werden.
- Du ein Multi-Agent-System baust, bei dem mehrere Agents ein gemeinsames State-Objekt teilen und aktualisieren müssen, statt nur Text hin und her zu übergeben.
Ein nützlicher Bauchcheck: Wenn du die Logik deines Agents als einen einzelnen Pfeil von links nach rechts zeichnen kannst, brauchst du noch keinen Graphen. In dem Moment, in dem du einen Pfeil zeichnest, der zurückschleift, oder zwei Pfeile, die aus derselben Box kommen, beschreibst du bereits einen Graphen – LangGraph gibt dieser Zeichnung einfach eine Ausführungs-Engine.
Migration von AgentExecutor zu LangGraph
Wenn du einen bestehenden Agent migrierst statt neu zu beginnen, ist der mechanische Teil kleiner als er aussieht:
- Modelliere deinen State. Nimm, was
AgentExecutordurch Aufrufe führte (normalerweise nurmessages), und definiere es alsTypedDict. - Wandle deine Tool-Calling-Schleife in zwei Nodes um. Ein Node ruft das Modell mit gebundenen Tools auf; ein
ToolNode(LangGraph liefert diesen vorgefertigt) führt alle angeforderten Tool-Calls aus. - Ersetze
max_iterationsdurch eine explizite bedingte Edge. Statt eines harten Limits schreibe eine Routing-Funktion, die entscheidet, ob zurück zum Modell-Node geschleift oder der Graph beendet werden soll – du kannst den Iterations-Zähler innerhalb dieser Funktion noch einschränken, wenn du dasselbe Sicherheitsnetz willst. - Füge einen Checkpointer hinzu, bevor du sonst etwas hinzufügst. Schon
MemorySaverin der Entwicklung gewöhnt dich daran, einethread_iddurch jeden Aufruf zu führen, was der Teil ist, den die meisten Migrationen vergessen, bis sie in der Produktion Dauerhaftigkeit brauchen. - Füge dann erst den Zweig oder Zyklus hinzu, den du wirklich brauchtest. Die Graph-Form, zu der du migrierst, sollte nicht komplexer sein als die
AgentExecutor-Schleife, die du hattest – zusätzliche Nodes sind für das neue Verhalten, das du hinzufügst, nicht für erforderliches Gerüst.
Das meiste davon ist ein paar Stunden Arbeit für einen kleinen Agent, kein Rewrite. Die größere Verschiebung ist mental: Du hörst auf, den Agent als „eine Schleife mit Tools" zu denken, und fängst an, ihn als „eine State-Machine mit einem Modell in einigen der Nodes" zu denken.
Wie es weitergeht
Dieser Post behandelt den Entscheidungspunkt – wann eine Chain nicht mehr ausreicht und ein Graph anfängt, sich zu lohnen. Der Rest dieser Serie greift von dort auf und geht tiefer in die Teile, die nur relevant sind, wenn du diesen Sprung gemacht hast:
- Human-in-the-Loop-Agents mit LangGraph – wie
interrupt()und ein Postgres-gestützter Checkpointer einem Graphen erlauben, für eine menschliche Überprüfung zu pausieren und Tage später ohne State-Verlust fortzufahren. - Einen MCP-Server in Python bauen – sobald die Tool-Oberfläche deines Agents wächst, behandelt das das Exponieren von Tools, Ressourcen und Prompts über das Model Context Protocol, statt sie manuell in deinen Graphen einzuverdrahten.
- LLM-Kosten senken ohne einen schlechteren Agent auszuliefern – Model-Routing, Prompt-Caching und Kontext-Trimming für Agents, die bei jedem Node ein Modell aufrufen.
- Strukturierte Outputs von LLMs, die in der Produktion nicht kaputt gehen – zuverlässige, schema-validierte Ausgaben aus den Modell-Aufrufen in den Nodes deines Graphen bekommen.
Fazit
AgentExecutor und StateGraph sind kein „Welches ist besser"-Vergleich – sie lösen unterschiedliche Mengen an Control-Flow-Komplexität. Eine Chain ist das richtige Werkzeug, wenn die Logik deines Agents wirklich eine Gerade mit ein paar Tool-Calls ist, und nach einem Graphen zu greifen, bevor du einen brauchst, fügt nur Nodes und Edges hinzu, die du für keinen Verhaltensgewinn warten musst. Aber in dem Moment, in dem dein Agent mit einem Zweck zyklen muss, auf ausgewertetem State verzweigen oder länger als eine einzige Prozesslebensdauer überleben, degeneriert eine Chain nicht gracefully – sie fängt an, Workarounds von außerhalb des Frameworks anzuhäufen. LangGraph existiert genau für diesen Moment, und zu wissen, auf welcher Seite davon du bist, bevor du anfängst, Orchestrierungs-Code zu schreiben, erspart dir später einen Rewrite.
Weiterlesen: Wie ein MCP-Server Softwareanalysen unterstützt, zeigt Reverse Engineer Anything (REA): Einrichtung und Praxis.

