Von einem Einzelknoten-Chatbot zu einem von MCP unterstützten Agenten in LangGraph
Erstellen Sie eine LangGraph-Anwendung Schicht für Schicht: Zustand und Reduzierer, Kanten, Tool-Loops, checkpointierte Threads, drei Streaming-Modi sowie über MCP bereitgestellte Tools.
Quellen: Building AI Applications with LangGraph: From a Basic Chatbot to Tools, Memory, Streaming and MCP
Die meisten LLM-Prototypen beginnen mit einem einzigen Aufruf: Ein Prompt wird gesendet und die Antwort ausgegeben. Diese Struktur bricht zusammen, sobald die Anwendung eine Websuche benötigt, eine Entscheidung darüber treffen muss, welcher Schritt als Nächstes ausgeführt wird, an frühere Gesprächsschritte erinnert werden muss oder ein Echtzeit-Progress im Benutzeroberfläche angezeigt werden soll. LangGraph löst dieses Problem, indem der Steuerfluss als expliziter Graph über einen gemeinsamen Zustand dargestellt wird. In dieser Übersichtsbeschreibung entwickelt sich eine Anwendung von einem Chatbot mit einem einzigen Knoten zu einem ReAct-Agenten, der Werkzeuge verwendet, über checkpoint-basiertes Gedächtnis verfügt und durch einen separaten MCP-Server mit Streaming-Funktionen versorgt wird. Zudem werden die Details aufgezeigt, die den Nutzern unterwegs oft Probleme bereiten.
Das mentale Modell: Der Zustand fließt über Kanten von Knoten zu Knoten
Eine einfache Integration eines LLM verläuft in gerader Linie von der Anfrage über das Modell bis zur Antwort. LangGraph verlangt hingegen, dass man in einem Kreislauf denkt: Ein State-Objekt tritt in das Graph ein, Nodes lesen es und geben Änderungen zurück, Edges entscheiden, welcher Node als Nächstes ausgeführt wird, und am Ende kommt der aktualisierte Zustand heraus. Die drei Bausteine sind daher:
- State: Die Daten, die zwischen den Schritten übertragen werden, wie beispielsweise die Nachrichtenhistorie.
- Nodes: Gewöhnliche Funktionen, die eine Aufgabe erledigen und einen teilweisen Update zurückgeben.
- Edges: Die Regeln, die die Nodes miteinander verbinden, einschließlich des Start- und Endpunkts einer Ausführung.
Das einfachste mögliche Graph enthält einen Node zwischen dem Eingangspunkt und dem Ausgangspunkt:
┌─────────────┐
│ State │
└──────┬──────┘
│
▼
┌─────────────┐
│ Node │
│ chatbot │
└──────┬──────┘
│
▼
┌─────────────┐
│ END │
└─────────────┘
Die Trennung von „welche Daten vorhanden sind“ und „was in welcher Reihenfolge ausgeführt wird“ ist die zentrale Gestaltungsentscheidung. Man kann den Ablauf ändern, ohne die Speicherung der Daten zu verändern, und den Zustand zu jedem Zeitpunkt überprüfen, ohne eine Kette von Funktionsaufrufen rückengängig machen zu müssen.
Warum ein Graph statt einer Kette von Aufrufen
Ein minimaler Chatbot ist ein einfacher Pfad vom Benutzer zum Modell und zurück:
User
↓
LLM
↓
Response
Nun fügen wir echte Anforderungen hinzu: eine Websuche, mehrere Tools, branchende Logik, Speicher, einen Schritt zur menschlichen Freigabe, mehrere Agenten, strömende Ausgaben sowie unterschiedliches Verhalten je nachdem, was das Modell zurückgibt. Jede dieser Anforderungen führt zu einem neuen Zweig oder einer Schleife, und verschachtelte if-Anweisungen um API-Aufrufe werden schnell schwer verständlich. Schon der häufigste Fall, „ein Tool aufrufen, wenn das Modell danach fragt“, enthält bereits einen Zyklus:
┌─────────────┐
│ LLM │
└──────┬──────┘
│
Need a tool?
/ \
Yes No
↓ ↓
Tool Response
│
↓
LLM
Sobald der Ablauf als Graph dargestellt wird, werden Verzweigungen und Schleifen zu sichtbaren Objekten, die man benennen, testen und darstellen kann – anstatt zu Kontrollflüssen, die in Hilfsfunktionen versteckt sind.
Schritt 1: Ein Chatbot mit einem einzigen Knoten und StateGraph
Die erste Version ist absichtlich einfach gehalten, mit einem Knoten, der zwischen den Start- und Endmarkern verbunden ist:
START
↓
Chatbot Node
↓
END
Definieren des Zustands und seines Reducers
Für einen Chatbot benötigt der Zustand hauptsächlich die Konversationshistorie. Ein erster Versuch besteht aus einem TypedDict mit einer messages-Liste:
from typing import TypedDict
class State(TypedDict):
messages: list
Konzeptionell sammelt diese Liste abwechselnd die Eingaben des Menschen und der KI auf:
State
└── messages
├── HumanMessage
├── AIMessage
├── HumanMessage
└── AIMessage
Ein Detail ist hier von großer Bedeutung. In LangGraph kann jeder Schlüssel im Zustand einen Reduzierer haben, der entscheidet, wie der Rückgabewert eines Knotens in den bestehenden Wert integriert wird. Eine einfache list-Anmerkung verfügt über keinen Reduzierer, wodurch ein Knoten, der {"messages": [reply]} zurückgibt, die gesamte Liste ersetzt anstelle, sie hinzuzufügen. Für Chatverläufe möchte man fast immer den add_messages-Reduzierer verwenden, der als Annotated[list, add_messages] deklariert ist (oder von der vorgefertigten MessagesState abgeleitet wird). Mit diesem werden zurückgegebene Nachrichten hinzugefügt, und Nachrichten mit passendem ID werden vor Ort aktualisiert. Der obige Auszug eignet sich gut als mentales Bild, doch in echtem Code sollte die annotierte Form verwendet werden.
Erstellung des Chatbot-Knotens
Ein Knoten ist einfach eine Funktion, die den aktuellen Zustand erhält und die Schlüssel zurückgibt, die geändert werden sollen. Der Chatbot-Knoten übermittelt den Verlauf an das Modell und gibt die Antwort zurück:
def chatbot(state: State):
return {
"messages": [
llm.invoke(state["messages"])
]
}
Lesen Sie es von innen nach außen. Der Funktion wird der gesamte Zustand übergeben:
state
Es werden die bisherigen Gesprächsinhalte extrahiert:
state["messages"]
Der Verlauf wird an das Modell gesendet:
llm.invoke(state["messages"])
Und die Antwort des Modells wird in einem Dictionary eingebettet, dessen Schlüssel vom aktualisierten Feld bestimmt werden. Beachten Sie, dass der Knoten den Zustand nicht direkt verändert; er gibt eine teilweise Aktualisierung zurück und lässt LangGraph diese zusammenführen:
{
"messages": [
llm.invoke(state["messages"])
]
}
Als Datenflussdiagramm befindet sich der Knoten zwischen zwei Versionen desselben Zustands:
State
│
│ messages
▼
┌─────────────┐
│ Chatbot │
│ Node │
└──────┬──────┘
│
│ LLM Response
▼
State
Registrierung des Knotens
Die Funktion wird Teil des Graphen, sobald sie unter einem Namen im Builder registriert wird:
graph_builder.add_node(
"llmchatbot",
chatbot
)
Der erste Argument ist die Identifikationsnummer des Knotens, auf die Kanten und Protokolle verweisen werden:
"llmchatbot"
Das zweite Argument ist die aufrufbare Funktion, die jedes Mal ausgeführt wird, wenn die Ausführung diesen Knoten erreicht:
chatbot
Schritt 2: Verbindung der Kanten und Kompilierung
Ein Knoten allein wird niemals ausgeführt; Kanten weisen LangGraph an, wie dorthin gelangt werden kann und wohin danach weitergegangen werden soll. Für dieses Diagramm sind zwei Kanten notwendig. Die erste verbindet den speziellen START-Markierer mit dem Chatbot:
graph_builder.add_edge(
START,
"llmchatbot"
)
In Diagrammform tritt die Ausführung in das Graph ein und geht direkt zum Knoten:
START
↓
llmchatbot
Die zweite Kante leitet die Steuerung vom Chatbot zu END, wodurch die Ausführung abgeschlossen wird:
graph_builder.add_edge(
"llmchatbot",
END
)
Das entspricht folgendem:
llmchatbot
↓
END
Zusammen bilden sie das vollständige, wenn auch minimale, Graph:
START
│
▼
┌────────────────┐
│ llmchatbot │
│ Node │
└────────┬───────┘
│
▼
END
Kompilierung und Aufruf
Durch das Kompilieren wird die Struktur überprüft (zum Beispiel, dass jede Kante auf einen bekannten Knoten verweist) und ein ausführbares Objekt erzeugt:
graph = graph_builder.compile()
Man führt es aus, indem man einen Anfangszustand übergeben wird. Einfache Rollen-/Inhaltsdictionaries werden akzeptiert und in LangChain-Nachrichtenobjekte umgewandelt:
result = graph.invoke({
"messages": [
{
"role": "user",
"content": "What is LangGraph?"
}
]
})
Im Hintergrund gelangt der Eingangs-Zustand in den Zustand START, der Chatbot-Knoten ruft das Modell auf, die Antwort wird in den Zustand eingefügt und der Ablauf endet:
Input State
↓
START
↓
llmchatbot
↓
LLM
↓
Updated State
↓
END
Der Rückgabewert von invoke ist der endgültige Zustand, sodass die Antwort des Modells der letzte Eintrag in result["messages"] ist.
Schritt 3: Darstellung des Graphen
Ein kompiliertes Diagramm kann sich selbst darstellen. In einem Jupyter-Notebook zeigen IPythons Anzeigehilfen ein PNG an, das aus einer Mermaid-Beschreibung des Graphen generiert wurde:
from IPython.display import Image, display
display(
Image(
graph.get_graph().draw_mermaid_png()
)
)
Bei diesem Diagramm ist das Bild kurz:
┌───────┐
│ START │
└───┬───┘
│
▼
┌──────────────┐
│ llmchatbot │
└──────┬───────┘
│
▼
┌───────┐
│ END │
└───────┘
Mit drei Kästchen ist der Wert nicht offensichtlich, wächst aber schnell. Sobald es bedingte Kanten, Tool-Node und Schleifen gibt, ist ein dargestelltes Diagramm der schnellste Weg, um zu überprüfen, ob das von Ihnen erstellte Graphen das ist, was Sie eigentlich bauen wollten. Beachten Sie, dass draw_mermaid_png() standardmäßig über einen externen Mermaid-Service gerendert wird und daher Netzwerkzugang benötigt; falls dieser nicht verfügbar ist, gibt draw_mermaid() den Mermaid-Text zurück, den Sie an anderer Stelle rendern können.
Schritt 4: Ein Suchwerkzeug für das Modell hinzufügen
Der Chatbot kann nur aus dem heraus antworten, was das Modell während des Trainings gelernt hat. Eine Frage zu aktuellen Entwicklungen in der agierenden KI erfordert aktuelle Informationen, weshalb im nächsten Schritt ein Web-Suchwerkzeug hinzugefügt wird. Im Beispiel wird Tavily Search verwendet, und der vorgesehene Ablauf sieht wie folgt aus:
User
↓
LLM
↓
Search required?
↓
Tavily Search
↓
Search Results
↓
LLM
↓
Final Answer
Das Binden von Werkzeugen ist nicht dasselbe wie deren Ausführen
Der erste Teil der Aufgabe besteht in der Tool-Bindung: Dabei wird dem Modell mitgeteilt, welche Tools vorhanden sind und welche Argumente sie entgegennehmen.
llm_with_tools = llm.bind_tools(
[tavily_search]
)
Nach der Bindung kann das Modell statt mit reinem Text mit einer strukturierten Tool-Aufrufanweisung antworten ("Aufruf von tavily_search mit dieser Abfrage"). Es kann diesen Aufruf selbst jedoch nicht ausführen. Etwas in Ihrer Anwendung muss den Tool-Aufruf erkennen, das Tool ausführen, das Ergebnis als ToolMessage hinzufügen und alles wieder an das Modell senden. Dass ein Modell Zugriff auf ein Tool hat, macht es noch nicht zu einem Agenten – der umgebende Ausführungsfloß tut das. Genau diesen Fluss stellt das Graph dar.
Schritt 5: Umwandlung in einen ReAct-Loop
ReAct verbindet Denken und Handeln: Das Modell überlegt sich die Aufgabe, entscheidet, ob es handeln muss, führt die Aktion mithilfe eines Tools aus, beobachtet das Ergebnis und setzt fort. In Graphenbegriffen wird die vorherige Gerade:
START
↓
LLM
↓
END
zu einem Graphen mit einem Entscheidungspunkt und einem Pfad, der zum Modell zurückführt:
START
│
▼
Agent
│
▼
┌─────────┐
│ LLM │
└────┬────┘
│
Need a tool?
/ \
Yes No
│ │
▼ ▼
Tool END
│
▼
Agent
│
▼
LLM
In LangGraph-Code ist diese Entscheidung eine bedingte Kante. Der vorgefertigte ToolNode führt aus, was auch immer in der letzten von einem Tool gesendeten AI-Nachricht enthalten ist, und tools_condition leitet bei vorhandenen Toolaufrufen zu diesem Knoten und bei deren Fehlen zu END. Eine Kante vom Toolknoten zurück zum Modell schließt den Kreislauf. Der vorgefertigte Hilfsfunktion create_react_agent erstellt für Sie dieselbe Struktur; prüfen Sie die aktuellen LangGraph-Dokumente nach deren genauer Position, da sich vorgefertigte Agent-Hilfsfunktionen im Laufe der Zeit zwischen Paketen verschoben haben.
Aufzeichnung eines Durchlaufs durch den Kreislauf
Nehmen wir an, der Benutzer bittet um die neuesten Nachrichten zu LangGraph. Ein Lauf des Kreislaufs erzeugt diese Abfolge von Nachrichten und Schritten:
HumanMessage
↓
Agent
↓
LLM
↓
"I need current information."
↓
Tool Call
↓
Tavily Search
↓
Search Results
↓
ToolMessage
↓
Agent
↓
LLM
↓
Final Answer
Der wichtige strukturelle Unterschied liegt im Zyklus. Der erste Chatbot war eine gerade Linie:
START → LLM → END
Das ReAct-Graph kann das Modell so oft wieder aufsuchen, wie es benötigt:
START
↓
Agent
↓
LLM
↓
Tool?
┌───────┐
│ │
Yes No
│ │
↓ ↓
Tool END
│
↓
Agent
Da Schleifen im Prinzip endlos laufen können, setzt LangGraph bei jedem Ausführungsvorgang eine Recursionsschranke (die über die Einstellung recursion_limit konfiguriert werden kann). Wenn ein Modell weiterhin Tools anfordert, erhält man einen Fehler statt einer endlosen Rechnung.
Schritt 6: Gespräche mit MemorySaver speichern
Bis jetzt beginnt jeder Aufruf von invoke von vorne. Betrachten wir zwei Nachrichtenaustausche. Zuerst stellt sich der Benutzer vor:
User:
My name is Himanshi.
Dann fragt er in einer separaten Anfrage nach:
User:
What is my name?
Die zweite Antwort ist nur möglich, wenn das Graph noch die erste Antwort enthält. LangGraph löst dieses Problem mit einem Checkpointer: Nach jedem Schritt wird ein Snapshot des Zustands gespeichert, und die nächste Ausführung im selben Gespräch setzt von diesem Snapshot aus fort. MemorySaver ist die einfachste Implementierung:
┌─────────────┐
│ Agent │
└──────┬──────┘
│
▼
┌───────────┐
│ State │
└─────┬─────┘
│
▼
┌─────────────┐
│ MemorySaver │
└─────────────┘
Man fügt ihn zur Kompilierzeit mit graph_builder.compile(checkpointer=memory) hinzu. Anstatt dass jeder Aufruf eine isolierte Ausführung darstellt, gehört jede Ausführung nun zu einem Gespräch, und ihr Zustand wird zwischen den Ausführungen beibehalten.
Threads trennen Gespräche voneinander
Der Schlüssel, der die Ausführungen miteinander verbindet, ist eine Thread-ID, die in der Konfiguration als {"configurable": {"thread_id": "..."}} übergeben wird. Jeder Thread hat seine eigene Historie und seinen eigenen aktuellen Zustand:
Thread 1
├── Message 1
├── Message 2
├── Message 3
└── Current State
Thread 2
├── Message 1
├── Message 2
└── Current State
Dadurch sehen zwei Benutzer in zwei Threads niemals die Nachrichten des anderen:
User A → Thread A → State A
User B → Thread B → State B
Zwei Einschränkungen gelten. MemorySaver speichert alles im Prozessgedächtnis, sodass die gesamte Historie beim Neustart verschwindet und nicht zwischen Serverinstanzen geteilt wird; in Produktivumgebungen werden persistente Checkpoint-Strukturen wie die von Postgres oder SQLite verwendet. In neueren Versionen wird die Klasse auch als InMemorySaver exportiert. Um genauer zu erfahren, was ein Checkpoint tatsächlich speichert, siehe wie Checkpoints, Schreibvorgänge und Blob-Daten in InMemorySaver zusammenwirken.
Schritt 7: Zustandsstrom während des Laufs des Graphen
Alles oben genannte nutzte den blockierenden Aufruf:
graph.invoke(...)
Es wartet darauf, dass das gesamte Diagramm abgeschlossen ist, bevor es zurückgegeben wird. Bei mehrstufigen Agenten kann das mehrere Sekunden lang zu keiner Aktivität auf dem Bildschirm führen, was für die Benutzer unangenehm ist. Das kompilierte Diagramm gibt außerdem stream frei, der während der Ausführung weiterhin Ausgaben liefert.
stream_mode="values">: der vollständige Zustand nach jedem Schritt
Der erste Modus, den man kennen sollte, ist:
stream_mode="values"
Wird in einer Schleife verwendet, liefert er nach jedem Schritt ein Abbild des gesamten Zustands:
for chunk in graph.stream(
input_state,
stream_mode="values"
):
print(chunk)
Jeder Abschnitt zeigt, wie der gesamte Zustand zu diesem Zeitpunkt aussieht, sodass man beobachten kann, wie er mit der Ausführung von Knoten und Werkzeugen wächst:
START
↓
State Update #1
↓
Node Execution
↓
State Update #2
↓
Tool Execution
↓
State Update #3
↓
END
Das ist praktisch, wenn der Zustand klein ist und man einfach jedes Mal „die neueste Version“ darstellen möchte.
stream_mode="updates">: nur die Änderungen jedes Knotens
Der zweite Modus ist:
stream_mode="updates"
Der Unterschied liegt darin, was jeder Datenblock enthält. In einem Modus erhält man den vollständigen Zustand:
values
Im anderen Modus erhält man nur den Delta-Unterschied, gekennzeichnet durch den Namen des Knotens, der ihn erzeugt hat:
updates
Eine einfache Möglichkeit, den Unterschied zu merken:
- values beantworten die Frage "Wie sieht der Zustand gerade aus?"
- updates beantworten die Frage "Was hat dieser spezielle Knoten gerade geändert?"
updates ist in der Regel die bessere Wahl für Debugging und Logging, denn es zeigt an, welcher Knoten für jede Änderung verantwortlich war, und hält die Datenblöcke klein, wenn der Zustand groß ist. Kein Modus streamt einzelne Token des Modells; für eine tokenweise Ausgabe gibt es einen separaten messages-Stream-Modus, und man kann eine Liste von Modi übergeben, um mehrere gleichzeitig zu erhalten.
Schritt 8: feinabgestimmte Sichtbarkeit mit astream_events
Zustandsaufnahmen und Knotendeltas zeigen an, was sich geändert hat, aber nicht alles, was geschehen ist. astream_events geht einen Schritt weiter und gibt für jeden Lebenszyklusmoment jedes Komponenten innerhalb der Ausführung ein Event aus:
Graph Execution
│
├── Graph Started
│
├── Node Started
│
├── LLM Started
│
├── LLM Streaming
│
├── LLM Completed
│
├── Tool Started
│
├── Tool Completed
│
├── Node Completed
│
└── Graph Completed
Man verarbeitet diese Events asynchron. Beachten Sie, dass der print-Aufruf im untenstehenden Beispiel innerhalb des Schleifenkörpers eingerückt werden muss; andernfalls weist Python einen Eingrifffehler aus:
async for event in graph.astream_events(
input_state,
version="v2"
):
print(event)
Jedes Event enthält einen Typ (zum Beispiel on_chat_model_stream oder on_tool_start), den Namen der Komponente sowie ihre Daten. Dadurch ist der Event-Stream nützlich für:
- Debugging und strukturiertes Protokollieren
- Allgemeine Überwachbarkeit von Agentenausführungen
- Aufzeichnung jeder LLM-Aufruf und jeder Tool-Aufruf
Ingenieure mit Hintergrundwissen im Backend werden dieses Muster erkennen: Es ähnelt eher der Verfolgung einer verteilten Anfrage als dem Lesen des Rückgabewerts einer einzelnen Funktion. Die Frage wandelt sich von „Hat der Agent funktioniert?“ zu „Was genau hat der Agent getan, in welcher Reihenfolge und wie lange hat jede Schritt gedauert?“
Wie die Komponenten zusammenwirken
Rückblickend fügte jeder Schritt eine weitere Funktion zum gleichen Grundgerüst hinzu. Es begann mit einem einzigen Modellaufruf:
START
↓
LLM
↓
END
Tools fügten eine Hin- und Rückreise in die Außenwelt hinzu:
START
↓
LLM
↓
Tool
↓
LLM
↓
END
Checkpointing sorgte dafür, dass der Loop sich Dinge merkte:
START
↓
Agent
↓
LLM ↔ Tools
↓
Memory / Checkpoint
↓
END
Streaming und Ereignisse machten den Ablauf für die Oberfläche sowie zur Überwachung zugänglich:
Graph
↓
Events
↓
UI / Logs / Observability
Keiner dieser Schritte erforderte eine Neuüberarbeitung der vorherigen; jeder stellt einen neuen Knoten, eine Kante, eine Kompilierungsoption oder einen Methodenaufruf im selben Graphen dar.
Schritt 9: Verlagern der Tools hinter einen MCP-Server
Bis jetzt befanden sich die Tools innerhalb der Anwendung. Das Model Context Protocol (MCP) verfolgt einen anderen Ansatz: Ein separater Server stellt Tools (und Ressourcen) bereit, und jeder MCP-Klient kann sie entdecken und aufrufen. Die Anwendung muss nicht länger jede Tool-Implementierung enthalten:
┌──────────────┐
│ MCP Client │
└──────┬───────┘
│
│
▼
┌──────────────┐
│ MCP Server │
└──────┬───────┘
│
┌──────────┼──────────┐
↓ ↓ ↓
add() multiply() weather()
Ein minimaler Server mit drei Tools
Ein kleiner Lernserver reicht aus, um die Architektur zu verstehen. Er stellt ein Additionstool bereit:
add(a, b)
ein Multiplikationstool:
multiply(a, b)
und ein Wettertool, das einen Stadtnamen entgegennimmt:
weather(city)
Das Wetterwerkzeug ruft keinen echten Wetterdienst auf. Es gibt einen festen String zurück, wodurch der Fokus auf dem Protokoll und nicht auf den API-Schlüsseln liegt:
"It's sunny in Mumbai."
Es geht um die Hin- und Rückreise, nicht um die Wettervorhersage:
Client
↓
MCP Server
↓
Tool
↓
Result
↓
Client
Tools müssen nicht mehr in der verwendenden Anwendung untergebracht sein
Die übergeordnete Idee ist, dass Tools unabhängig von den Anwendungen, die sie nutzen, gehostet werden können. Die Anwendung kommuniziert mit einem MCP-Client, der Client spricht das Protokoll mit dem Server, und der Server ist für die Implementierungen verantwortlich:
Application
│
▼
MCP Client
│
MCP Protocol
│
▼
MCP Server
/ | \
/ | \
↓ ↓ ↓
Add Multiply Weather
Dadurch werden wiederverwendbare Tool-Server möglich. Ein Team könnte einen Server betreiben, der eine ganze Kategorie an Funktionen bereitstellt:
Database Tools
File Tools
Git Tools
Search Tools
Weather Tools
Internal APIs
Und mehrere verschiedene Agenten, Chat-Cliente oder IDE-Integrationen können denselben Server nutzen, ohne Code zu duplizieren.
Schritt 10: Kombination von LangGraph und MCP
Die beiden Technologien lösen unterschiedliche Aspekte des Problems. LangGraph verwaltet den Workflow: Zustände, Routing, Schleifen, Speicher und Streaming. Der MCP-Standard standardisiert die Art und Weise, wie Tools und Ressourcen bereitgestellt werden. Bei einer kombinierten Architektur kommuniziert der Tool-Node des Agents mit einem MCP-Klienten anstelle davon, lokale Funktionen aufzurufen:
User
│
▼
LangGraph Agent
│
▼
LLM
│
Need a tool?
│
▼
MCP Client
│
▼
MCP Server
/ | \
↓ ↓ ↓
Math Search Weather
Der Agent muss nicht wissen, wie ein Tool implementiert ist. Er muss lediglich wissen, welche Funktionen vorhanden sind und wie man sie aufruft – der MCP-Server kümmert sich um den Rest. In Python ist das langchain-mcp-adapters-Paket eine gängige Methode, um MCP-Tools als LangChain-Tools zu laden, damit sie an ein Modell gebunden und in einem ToolNode verwendet werden können. Der vorhandene Artikel unter Standardisierung der Tool-Integration in LangGraph mit MCP behandelt diese Integration ausführlicher.
Ein vollständiges mentales Modell
Zusammengefasst sieht ein LangGraph-Agent so aus: Die Eingabe des Benutzers wird zum Zustand, die Knoten arbeiten damit, das Modell entscheidet, ob ein Tool benötigt wird, die Ergebnisse des Tools fließen zurück ins Modell und der Ablauf endet mit einer Antwort:
USER
│
▼
┌────────────┐
│ LangGraph │
│ Agent │
└──────┬─────┘
│
▼
STATE
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
NODE NODE NODE
│ │ │
└─────────────┼─────────────┘
│
▼
LLM
│
Need a tool?
/ \
Yes No
│ │
▼ ▼
TOOL RESPONSE
│
▼
RESULT
│
└──────► LLM
│
▼
END
Rund um diese Kernschleife befinden sich die Funktionen, die je nach Bedarf aktiviert werden:
┌──────────────────────────────┐
│ Capabilities │
│ │
│ Memory / Checkpointing │
│ Streaming │
│ Event Tracking │
│ MCP │
│ Tools │
└──────────────────────────────┘
Wo LangChain endet und LangGraph beginnt
Da LangGraph in der Regel nach LangChain erlernt wird, ist es hilfreich, ihre Rollen voneinander zu trennen. LangChain stellt die Bausteine für LLM-Anwendungen bereit: Modellumhüllungen, Nachrichten, Tools, Prompts sowie strukturierte Ausgaben. LangGraph orchestriert diese Bausteine zu expliziten, zustandsbasierten Workflows. Es handelt sich um ergänzende Schichten, keine konkurrierenden Optionen – der größte Teil des LangGraph-Code verwendet interne LangChain-Komponenten in seinen Knoten.
Lektionen, die auf größere Graphen übertragen werden
Mehrere Prinzipien aus diesem kleinen Projekt gelten unverändert für weitaus größere Systeme.
Zustand ist die Grundlage
Anstatt Daten über Funktionsargumente zu übertragen, liest jeder Knoten von einem gemeinsamen Zustand und schreibt in ihn, wobei Reducer festlegen, wie Updates zusammengeführt werden:
State
↓
Node
↓
Updated State
↓
Next Node
Knoten sind Arbeitseinheiten
Jeder Knoten sollte eine erkennbare Aufgabe ausführen, was komplexe Arbeitsabläufe einfacher zerlegen und testen lässt:
chatbot node
search node
tool node
validation node
human approval node
Kanten kodieren den Arbeitsablauf
Eine Kante beantwortet eine Frage: Wenn dieser Knoten abgeschlossen ist, wohin geht die Ausführung danach? Einfache Kanten bilden Sequenzen; bedingte Kanten erzeugen Verzweigungen und Schleifen.
Diagramme sind besser als Code-Lesen
Bei einem Graphen mit vielen Knoten und bedingten Pfaden vermittelt ein dargestelltes Diagramm die Architektur schneller als das Lesen des Erstellungscode:
Node → Decision → Tool → Agent → END
Checkpointe sind mehr als Chatverläufe
Ein Checkpointer speichert den vollständigen Zustand des Graphen, nicht nur die Nachrichten. Das ermöglicht mehrstufige Gespräche und bildet zugleich die Grundlage für langlaufende Agenten, Pausen mit menschlicher Einmischung sowie die Wiederaufnahme nach Fehlern.
Streaming dient sowohl Nutzern als auch Entwicklern
Streaming verbessert das wahrgenommene Reaktionsvermögen, und dieselben Ausgaben helfen bei der Fehlersuche sowie beim Beobachten von Abläufen.
Ein Agentenaufruf umfasst viele Operationen
Mit astream_events wird deutlich, dass ein Agentenaufruf eine verschachtelte Abfolge von Graph-, Knoten-, Modell- und Tool-Operationen ist:
Graph
↓
Node
↓
LLM
↓
Tool
↓
LLM
↓
Node
↓
Graph
Je komplexer die Anwendung, desto dringender ist diese Sichtbarkeit.
MCP entkoppelt Tools von Anwendungen
Über MCP bereitgestellte Tools können zwischen Anwendungen geteilt werden und unabhängig von den Agenten, die sie aufrufen, weiterentwickelt werden.
Was als Nächstes?
Diese Grundlage eröffnet mehrere Richtungen, die es wert sind, erforscht zu werden: bedingte Kanten und parallele Zweige, Unterbrechungen durch menschliches Eingreifen, Langzeitgedächtnis und dauerhafte Checkpoints, generatives Verfahren unter Verwendung von Abrufdaten innerhalb eines Graphen, Mehr-Agentensysteme, tiefere Integrationen von MCP, Überwachbarkeit und Bewertung von Agenten, Produktivumsetzung sowie Fehlertoleranz durch Wiederholungsversuche.
Der gemeinsame Nenner ist, dass eine KI-Anwendung nicht einfach nur ein Modell ist, das Text erzeugt. Es handelt sich um ein um das Modell konzipiertes System, das eine Anfrage entgegennehmen kann, entscheiden, handeln, das Ergebnis beobachten, es sich merken und fortfahren kann:
Understand
↓
Decide
↓
Act
↓
Observe
↓
Remember
↓
Continue
Ein Graph ist eine natürliche Art, diesen Kreislauf darzustellen.
Zusammenfassung
Die gesamte Entwicklung begann mit dem kleinstmöglichen Graphen:
START → Chatbot → END
und wuchs Schicht für Schicht zu einem System mit Werkzeugen, Agentenzyklen, Speicher, Streaming-Technologien und MCP aus:
┌─────────────┐
│ LLM │
└──────┬──────┘
│
▼
┌─────────────┐
│ Tools │
└──────┬──────┘
│
▼
┌─────────────┐
│ Agent │
└──────┬──────┘
│
┌──────────┼──────────┐
↓ ↓ ↓
Memory Streaming MCP
│ │ │
└──────────┼──────────┘
↓
AI Application
Zusammen betrachtet wirken die Funktionen nicht mehr wie eine Liste, sondern wie eine einzige Architektur:
- State speichert Daten und über Checkpoints auch den Speicherinhalt; den Nachrichtenlisten wird der Reducer
add_messageszugewiesen. - Nodes erledigen die Arbeit und geben teilweise Aktualisierungen zurück.
- Edges definieren den Fluss, wobei bedingte Kanten diesen in einen Kreislauf verwandeln.
- The model liefert das Denkvermögen und die Sprache; tools ermöglichen es ihm, auf die Außenwelt einzuwirken.
- Streaming und Events sorgen dafür, dass jeder Ablauf beobachtbar ist – von UI-Aktualisierungen bis zu Spuren.
- MCP standardisiert die Darstellung von Tools, sodass sie außerhalb des Agenten existieren können, der sie verwendet.