Startseite / Artikel

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.

Verlag: REACTAPP.TOP18 Min. Lesezeit4252 Wörter

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
  • Erstellung einer benutzerdefinierten Streaming-Oberfläche, die Tokens sowie Aktivitäten der Tools anzeigt
  • Erfassung, wo die Zeit verbraucht wird
  • 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_messages zugewiesen.
    • 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.