Python & AI Tutorials Logo
LangChain & LangGraph

16. Vorgefertigte Komponenten und Multi-Branch-Routing

In Kapitel 15 haben wir einen Agent-Graphen von Hand zusammengesetzt — einen Model-Node, einen Tool-Node und eine Conditional Edge, die entscheidet, ob die Schleife fortgesetzt oder gestoppt wird. Dies ist im Grunde die Standardstruktur für tool-aufrufende Agents, weshalb LangChain und LangGraph sie als vorgefertigte Komponenten (prebuilt components) mitliefern, die Sie verwenden können, anstatt jedes Mal dasselbe Gerüst von Grund auf neu zu schreiben.

In der ersten Hälfte dieses Kapitels bauen wir den Agent aus Kapitel 15 mit vorgefertigten Komponenten neu auf. Wir ersetzen den Node für die Tool-Ausführung und die Routing-Funktion durch ToolNode und tools_condition und ersetzen schließlich den gesamten Graph-Aufbau durch einen einzigen Aufruf von create_agent. Sie werden sehen, dass das Verhalten identisch mit Kapitel 15 bleibt, während der Code erheblich schrumpft.

In der zweiten Hälfte kombinieren wir vorgefertigte Komponenten mit dem Ansatz des manuellen Graph-Aufbaus aus Kapitel 15, um einen komplexeren Agent zu bauen. Der Agent, den wir bauen, leitet jede Anfrage an einen anderen Handler weiter — komplexe Beratungen gehen an ein Hochleistungsmodell, während einfache Fragen von einem günstigeren, kleineren Modell beantwortet werden. Dies ist eine Multi-Branch-Struktur, bei der sich der Verarbeitungspfad je nach Art der Anfrage verzweigt.

16.1) Vorgefertigte Komponenten und create_agent

In diesem Abschnitt ersetzen wir die Funktion tool_node und die Funktion should_continue aus dem Graphen von Kapitel 15 durch die vorgefertigten Komponenten ToolNode und tools_condition. Danach überspringen wir den manuellen Aufbau komplett und erstellen den gesamten Graphen mit einem einzigen create_agent-Aufruf. Achten Sie bei jedem Schritt darauf, dass der Code kürzer wird, während das Verhalten des Agents identisch mit Kapitel 15 bleibt.

16.1.1) ToolNode und tools_condition

ToolNode ist ein vorgefertigter Node, der die Tool-Ausführung für Sie übernimmt. Wenn die letzte Nachricht im State (die vom LLM zurückgegebene AIMessage) tool_calls enthält, führt er die angeforderten Tools aus und fügt die Ergebnisse als ToolMessage-Objekte zu messages hinzu. Er erledigt dieselbe Aufgabe wie die Funktion tool_node, die wir in Kapitel 15 geschrieben haben. Darüber hinaus führt er die Tools parallel aus, wenn das LLM mehrere Tools gleichzeitig anfordert.

Er unterstützt auch die Ausnahmebehandlung während der Tool-Ausführung. Wenn Sie ToolNode(tools, handle_tool_errors=True) setzen, stürzt der Graph selbst dann nicht ab, wenn ein Tool eine Ausnahme auslöst. Die Ausnahme wird in eine ToolMessage umgewandelt, die die Fehlerdetails enthält und an das LLM weitergegeben wird, damit es den Fehler erkennen und mit korrigierten Argumenten erneut versuchen kann.

Sie erstellen einen ToolNode, indem Sie ihm eine Liste von Tools übergeben. Wir verwenden dieselben zwei Tools aus Kapitel 15.

python
from langgraph.prebuilt import ToolNode
from langchain.tools import tool
 
@tool
def get_weather(city: str) -> str:
    """Ruft das aktuelle Wetter für eine Stadt ab."""
    fake_data = {"Tokyo": "18°C, bewölkt", "Cairo": "31°C, sonnig"}
    return fake_data.get(city, f"Keine Wetterdaten für {city}.")
 
@tool
def calculate(expression: str) -> str:
    """Wertet einen einfachen arithmetischen Ausdruck aus. Beispiel: '3 * 21'."""
    return str(eval(expression))  # Warnung: eval() ist ein Sicherheitsrisiko. Nicht in Produktion verwenden.
 
tools = [get_weather, calculate]
 
# Die tool_map + tool_node-Funktion aus Kapitel 15, ersetzt durch diese einzige Zeile
tool_node = ToolNode(tools)

ToolNode erstellt aus der Tool-Liste eine interne Zuordnung von Namen zu Tools, genau wie die tool_map aus Kapitel 15. Zur Laufzeit sucht er jedes Tool anhand des vom LLM angeforderten Namens und ruft es auf. Mit anderen Worten: das tool_map-Dictionary, die for-Schleife über tool_calls und der Code, der ToolMessage-Objekte erstellt und sammelt — all das befindet sich jetzt innerhalb von ToolNode.

tools_condition ist die vorgefertigte Routing-Funktion, die die Funktion should_continue aus Kapitel 15 ersetzt. Sehen wir uns should_continue aus Kapitel 15 noch einmal an.

python
def should_continue(state: AgentState) -> Literal["tool_node", "__end__"]:
    """Entscheidet, ob Tools ausgeführt oder der Graph beendet werden soll."""
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "tool_node"
    return END

Sie gab "tool_node" (den registrierten Namen unseres Tool-Nodes) zurück, wenn die letzte Nachricht tool_calls hatte, und andernfalls END. tools_condition funktioniert genau gleich, mit einem Unterschied beim Namen, den es zurückgibt. Während should_continue so geschrieben war, dass es "tool_node" zurückgibt — den Namen, den wir in unserem Graphen registriert haben — ist tools_condition fest darauf codiert, "tools" zurückzugeben.

Wie wir in Abschnitt 15.2.4 gelernt haben, ist der Wert, den eine Routing-Funktion zurückgibt, der Name des als Nächstes auszuführenden Nodes. Wenn im Graphen kein Node mit diesem Namen existiert, schlägt das Routing fehl. Wenn Sie also tools_condition verwenden, muss der Tool-Node unter dem Namen "tools" registriert sein.

python
from langgraph.prebuilt import ToolNode, tools_condition
 
builder.add_node("tools", ToolNode(tools))                  # Registrierung mit dem Namen "tools"
builder.add_conditional_edges("llm_call", tools_condition)  # Routet zu "tools" oder END

Auf diese Weise verbindet sich tools_condition, wenn es "tools" zurückgibt, genau mit dem ToolNode, den wir gerade registriert haben.

Setzen wir nun den vollständigen Graphen aus Kapitel 15 wieder zusammen, einschließlich aller verbleibenden Teile. Die Tools, der State und der llm_call-Node sind gegenüber Kapitel 15 unverändert.

python
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.tools import tool
 
@tool
def get_weather(city: str) -> str:
    """Ruft das aktuelle Wetter für eine Stadt ab."""
    fake_data = {"Tokyo": "18°C, bewölkt", "Cairo": "31°C, sonnig"}
    return fake_data.get(city, f"Keine Wetterdaten für {city}.")
 
@tool
def calculate(expression: str) -> str:
    """Wertet einen einfachen arithmetischen Ausdruck aus. Beispiel: '3 * 21'."""
    return str(eval(expression))  # Warnung: eval() ist ein Sicherheitsrisiko. Nicht in Produktion verwenden.
 
tools = [get_weather, calculate]
 
class AgentState(MessagesState):
    llm_calls: int
 
llm = ChatOpenAI(model="gpt-5-mini")
model_with_tools = llm.bind_tools(tools)
 
def llm_call(state: AgentState):
    """Ruft das LLM auf und gibt seine Antwort zurück."""
    response = model_with_tools.invoke(state["messages"])
    return {
        "messages": [response],
        "llm_calls": state.get("llm_calls", 0) + 1,
    }
 
builder = StateGraph(AgentState)
 
builder.add_node("llm_call", llm_call)
builder.add_node("tools", ToolNode(tools))  # ToolNode statt der tool_node-Funktion aus Kapitel 15
 
builder.add_edge(START, "llm_call")
builder.add_conditional_edges("llm_call", tools_condition)  # tools_condition statt should_continue aus Kapitel 15
builder.add_edge("tools", "llm_call")
 
agent = builder.compile()

Vergleichen Sie dies mit dem Code aus Kapitel 15. Das tool_map-Dictionary, die Funktion tool_node und die Funktion should_continue sind alle verschwunden. Die Arbeit, die sie erledigten, wird jetzt von ToolNode(tools) und tools_condition übernommen. Der Tool-Node ist als "tools" registriert, um dem Namen zu entsprechen, zu dem tools_condition routet. Führen wir ihn mit derselben Frage aus Kapitel 15 aus.

python
result = agent.invoke({
    "messages": [HumanMessage(content="Ermittle die Temperatur in Cairo und multipliziere die Zahl dann mit 3.")],
    "llm_calls": 0,
})
 
print(result["messages"][-1].content)
print(f"\nGesamtzahl der LLM-Aufrufe: {result['llm_calls']}")

Ausgabe:

Aktuelle Temperatur in Cairo: 31°C. Multipliziert mit 3 = 93.
 
Gesamtzahl der LLM-Aufrufe: 3

Das Ergebnis ist identisch mit Kapitel 15. Der Agent schlägt das Wetter nach, führt die Berechnung durch und erzeugt die endgültige Antwort — das gleiche Verhalten bleibt erhalten, während der Code, den wir schreiben und pflegen müssen, geschrumpft ist.

Was, wenn Sie den Tool-Node unter einem anderen Namen als "tools" registrieren möchten? In diesem Fall übergeben Sie ein Zuordnungs-Dictionary als drittes Argument an add_conditional_edges und geben an, mit welchem Node jeder Rückgabewert von tools_condition verbunden werden soll. Da tools_condition entweder "tools" oder END zurückgibt, verwenden Sie diese als Schlüssel und ordnen sie den Ziel-Nodes zu. Wenn Sie den Tool-Node beispielsweise als "run_tools" registrieren:

python
builder.add_node("run_tools", ToolNode(tools))
builder.add_conditional_edges(
    "llm_call",
    tools_condition,
    {"tools": "run_tools", END: END}   # Rückgabe "tools" → run_tools-Node, Rückgabe END → beenden
)

ToolNode und tools_condition ersetzen einzelne Teile des Graphen — die mühsamen — aber das Hinzufügen von Nodes und ihre Verdrahtung liegt weiterhin bei uns. Könnten wir auch diesen Aufbau abgeben? Genau das macht create_agent.

16.1.2) create_agent

create_agent ist eine LangChain-Factory-Funktion, die den gesamten Graph-Aufbau für einen tool-aufrufenden Agent übernimmt. Übergeben Sie ihr ein Modell und eine Liste von Tools, und sie baut einen Graphen mit derselben Struktur auf, die wir in 16.1.1 zusammengesetzt haben — bereits kompiliert und einsatzbereit. Probieren wir es aus.

python
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_core.messages import HumanMessage
 
@tool
def get_weather(city: str) -> str:
    """Ruft das aktuelle Wetter für eine Stadt ab."""
    fake_data = {"Tokyo": "18°C, bewölkt", "Cairo": "31°C, sonnig"}
    return fake_data.get(city, f"Keine Wetterdaten für {city}.")
 
@tool
def calculate(expression: str) -> str:
    """Wertet einen einfachen arithmetischen Ausdruck aus. Beispiel: '3 * 21'."""
    return str(eval(expression))  # Warnung: eval() ist ein Sicherheitsrisiko. Nicht in Produktion verwenden.
 
agent = create_agent(
    model="openai:gpt-5-mini",
    tools=[get_weather, calculate],
    system_prompt="Sie sind ein hilfreicher Assistent.",
)
 
result = agent.invoke({
    "messages": [HumanMessage(content="Ermittle die Temperatur in Cairo und multipliziere die Zahl dann mit 3.")],
})
print(result["messages"][-1].content)

Ausgabe:

Aktuelle Temperatur in Cairo: 31°C. Multipliziert mit 3 = 93.

Keine State-Definition, keine Node-Funktionen, kein add_node oder add_edge. Ein einziger create_agent-Aufruf hat all das erledigt, und das Ergebnis ist identisch mit 16.1.1.

Was intern passiert, ist genau das, was wir bereits kennen. create_agent erstellt aus dem übergebenen Modell einen LLM-Aufruf-Node, baut aus der Tool-Liste einen ToolNode und verbindet sie mit einer tools_condition-Edge und einer Rückschleifen-Edge. Das Ergebnis ist ein Graph mit Schleifenstruktur, der identisch mit dem ist, was wir in 16.1.1 zusammengesetzt haben.

tool_calls vorhanden

keine tool_calls

START

LLM-Aufruf-Node

ToolNode

END

Sehen wir uns die Parameter an. create_agent ist für uns eigentlich nicht neu — wir haben es in Kapitel 11 kurz beim Bau von konversationellem RAG verwendet, sind aber nicht im Detail auf die Parameter eingegangen. Gehen wir sie einzeln durch.

  • model: Das LLM, das der Agent verwenden wird. Der einfachste Ansatz besteht darin, einen Provider-String wie "openai:gpt-5-mini" zu übergeben. Wenn Sie Modellparameter direkt konfigurieren müssen, übergeben Sie eine initialisierte Modellinstanz wie ChatOpenAI(model="gpt-5-mini"). Intern verwendet der LLM-Aufruf-Node dieses Modell.
  • tools: Die Liste der Tools, die der Agent verwenden kann. Daraus wird intern ein ToolNode gebaut.
  • system_prompt: Verhaltensanweisungen für den Agent. Diese werden bei jedem LLM-Aufruf als Systemnachricht der Nachrichtenliste vorangestellt.
  • checkpointer: Speichert den Konversationszustand, damit sich der Agent an vorherige Turns erinnern kann. Dies ist derselbe Parameter, den wir in Kapitel 11 mit InMemorySaver() und thread_id verwendet haben, um Multi-Turn-Konversationen zu implementieren. Wie er funktioniert, behandeln wir im Detail in Kapitel 17.
  • response_format: Verwenden Sie dies, wenn Sie die endgültige Antwort des Agents als strukturierte Ausgabe wünschen. Übergeben Sie ein Pydantic-Modell (dasselbe Konzept aus Kapitel 7), und das validierte Objekt ist in result["structured_response"] verfügbar.
  • middleware: Registriert Funktionen, die an bestimmten Punkten in der Ausführungsschleife des Agents laufen. Dies ist der Parameter, mit dem wir in Kapitel 11 trim_old_messages registriert haben. Wir erklären ihn weiter unten im Detail.

Sehen wir uns an, wie response_format in der Praxis funktioniert.

python
from pydantic import BaseModel
from langchain.agents import create_agent
 
class WeatherReport(BaseModel):
    city: str
    temperature: str
    condition: str
 
agent = create_agent(
    model="openai:gpt-5-mini",
    tools=[get_weather, calculate],
    response_format=WeatherReport,
)
 
result = agent.invoke({
    "messages": [HumanMessage(content="Wie ist das Wetter in Tokyo?")],
})
print(result["structured_response"])

Ausgabe:

city='Tokyo' temperature='18°C' condition='bewölkt'

Der Agent rief das Tool get_weather auf und ordnete die Informationen dann in ein WeatherReport-Objekt ein, das dem Schema entspricht.

middleware

Die Agent-Schleife hat verschiedene Phasen. Sie ruft das LLM auf, führt Tools aus, ruft das LLM erneut auf — diese Phasen wiederholen sich. Middleware ermöglicht es Ihnen, Ihre eigenen Funktionen vor oder nach diesen Phasen einzufügen. Sie geben den Zeitpunkt mit einem Decorator an: @before_model bedeutet direkt vor dem LLM-Aufruf und @after_model bedeutet direkt nachdem das LLM geantwortet hat. Registrieren Sie die Funktion im Parameter middleware, und sie läuft jedes Mal an dem festgelegten Punkt.

Wir haben Middleware bereits in Kapitel 11 verwendet. Wir haben eine trim_old_messages-Funktion mit @before_model dekoriert und registriert — da sie vor jedem LLM-Aufruf lief, konnte sie die Nachrichtenliste jedes Mal kürzen.

Bauen wir eine Middleware, die die Anzahl der Nachrichten direkt vor jedem LLM-Aufruf ausgibt, damit wir genau sehen können, wann sie läuft.

python
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import before_model
 
@before_model
def log_llm_call(state: AgentState, runtime) -> None:
    """Gibt die Anzahl der Nachrichten direkt vor jedem LLM-Aufruf aus."""
    print(f"[before_model] LLM wird gleich aufgerufen, aktuelle Nachrichten: {len(state['messages'])}")
 
agent = create_agent(
    model="openai:gpt-5-mini",
    tools=[get_weather, calculate],
    middleware=[log_llm_call],
)
 
result = agent.invoke({
    "messages": [HumanMessage(content="Ermittle die Temperatur in Cairo und multipliziere die Zahl dann mit 3.")],
})
print(result["messages"][-1].content)

Ausgabe:

[before_model] LLM wird gleich aufgerufen, aktuelle Nachrichten: 1
[before_model] LLM wird gleich aufgerufen, aktuelle Nachrichten: 3
[before_model] LLM wird gleich aufgerufen, aktuelle Nachrichten: 5
Aktuelle Temperatur in Cairo: 31°C. Multipliziert mit 3 = 93.

Die Middleware log_llm_call lief dreimal. Das LLM wurde während der Verarbeitung der Anfrage des Benutzers dreimal aufgerufen, und die Middleware lief direkt vor jedem Aufruf. Die Nachrichtenzahlen verraten uns den State an jedem Punkt: vor dem ersten Aufruf gab es nur die Frage des Benutzers (HumanMessage) — 1 Nachricht. Nach jeder Schleifeniteration wurden die AIMessage, die einen Tool-Aufruf anforderte, und die ToolMessage mit dem Ergebnis hinzugefügt, sodass die Zahl auf 3 und dann auf 5 anwuchs.

Eine Sache, die Sie wissen sollten: Vor LangChain 1.0 wurde diese Rolle von einer Funktion namens create_react_agent auf der LangGraph-Seite ausgefüllt, die jetzt veraltet ist. Wenn Sie from langgraph.prebuilt import create_react_agent in älteren Tutorials oder Blogbeiträgen sehen, verstehen Sie es als eine frühere Version des create_agent, das Sie jetzt lernen.

Wir haben den Agent aus Kapitel 15 mit ToolNode, tools_condition und create_agent kompakt neu aufgebaut. Im nächsten Abschnitt kombinieren wir diese vorgefertigten Komponenten mit manuellem Graph-Aufbau, um einen komplexeren Agent zu bauen.

16.2) Einen Multi-Branch-Agent bauen

Der Multi-Branch-Agent, den wir in diesem Abschnitt bauen, ermittelt zunächst, mit welcher Art von Anfrage er es zu tun hat, und behandelt dann jede Art mit einem anderen Modell oder einem anderen Satz von Tools. Wir setzen den Gesamtgraphen manuell mit dem Ansatz aus Kapitel 15 zusammen und verwenden create_agent für die Teile, die eine tool-aufrufende Schleife benötigen.

16.2.1) Anforderungen und Design

Wir bauen den Kundensupport-Agent, der in der Einleitung dieses Kapitels erwähnt wurde. Hier sind die Anforderungen:

  • Einfache Anfragen ("Wie sind Ihre Öffnungszeiten?") → Ein kostengünstiges kleines Modell antwortet direkt.
  • Komplexe Beratungen ("Meine Bestellung kam beschädigt an — sollte ich einen Umtausch oder eine Rückerstattung bekommen?") → Ein Hochleistungsmodell antwortet.
  • Bestellabfragen ("Wie ist der Lieferstatus von Bestellung #12345?") → Ein Agent mit einem Bestellabfrage-Tool übernimmt sie.

Jeder Anfragetyp benötigt eine andere Modell- und Tool-Konfiguration, daher brauchen wir einen Graphen, der jede Anfrage zuerst klassifiziert und sie dann an den richtigen Handler weiterleitet. Hier ist die Struktur:

einfache Anfrage

komplexe Beratung

Bestellabfrage

START

classify

Anfragetyp?

simple_handler

complex_handler

order_agent

END

Wenn eine Anfrage eingeht, ermittelt der classify-Node, um welchen Anfragetyp es sich handelt, und speichert das Ergebnis im State. Eine Conditional Edge liest dann den gespeicherten Typ aus dem State und routet zum passenden Node. simple_handler bearbeitet einfache Anfragen und complex_handler befasst sich mit komplexen Beratungen. order_agent verwendet ein Bestellabfrage-Tool, um den Lieferstatus zu prüfen und zu antworten. Da order_agent ein standardmäßiger tool-aufrufender Node ist, bauen wir ihn mit create_agent. Bauen wir nun jedes Teil der Reihe nach.

16.2.2) Der Classify-Node und die Routing-Funktion

Definieren wir zunächst den State. Wir fügen ein intent-Feld hinzu, um das Klassifikationsergebnis zu speichern. Die drei Typen werden durch die Werte "simple", "complex" und "order" dargestellt.

python
from langgraph.graph import MessagesState
 
class State(MessagesState):
    intent: str    # Klassifikationsergebnis: "simple", "complex", "order"

Als Nächstes kommt der Classify-Node. Er verwendet ein LLM, um zu ermitteln, zu welchem der drei Typen die Anfrage des Benutzers gehört, und speichert das Ergebnis in intent. Wir erhalten das Klassifikationsergebnis über strukturierte Ausgabe, die wir in Kapitel 7 gelernt haben. Wenn wir das intent-Feld des Schemas mit einem Literal-Typ deklarieren, wird die Antwort des LLM auf einen der deklarierten Werte beschränkt.

python
from typing import Literal
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
 
class IntentRoute(BaseModel):
    """Klassifikationsergebnis für eine Kundenanfrage."""
    intent: Literal["simple", "complex", "order"] = Field(
        description=(
            "simple: allgemeine Fragen wie Öffnungszeiten oder Begrüßungen. "
            "complex: Beratungen, die sorgfältiges Nachdenken erfordern, wie Streitfälle oder Rückerstattungen. "
            "order: Anfragen zur Abfrage einer bestimmten Bestellung."
        )
    )
 
classifier_llm = ChatOpenAI(model="gpt-5.4-nano").with_structured_output(IntentRoute)
 
def classify(state: State):
    """Klassifiziert den Typ der Kundenanfrage."""
    question = state["messages"][-1].content
    result = classifier_llm.invoke(
        f"Klassifiziere die Anfrage des Kunden.\n\nAnfrage: {question}"
    )
    return {"intent": result.intent}

Wir haben das kleinste Modell (gpt-5.4-nano) für die Klassifikation verwendet. Zu entscheiden „Welcher Typ ist diese Anfrage?" ist eine einfache Aufgabe, die kein Hochleistungsmodell benötigt. Und da der Classify-Node ein Gateway ist, das jede Anfrage durchläuft, ist ein günstiges und schnelles Modell vorzuziehen.

Als Nächstes kommt die Routing-Funktion. Sie gibt einfach das im State gespeicherte Klassifikationsergebnis zurück.

python
def route_by_intent(state: State) -> Literal["simple", "complex", "order"]:
    """Bestimmt den nächsten Node basierend auf dem Klassifikationsergebnis."""
    return state["intent"]

Der classify-Node hat bereits entschieden, welcher Node als Nächstes laufen soll, und es in intent gespeichert, sodass die Routing-Funktion diesen Wert einfach unverändert zurückgibt.

16.2.3) Handler-Nodes nach Typ

Bauen wir nun die Handler für jeden der drei Anfragetypen.

Der Handler für einfache Anfragen ruft einmal ein kleines Modell auf. In einem echten Kundensupport-Agent würden Sie RAG anwenden, um interne Dokumente nach Antworten zu durchsuchen, aber wir haben den Handler einfach gehalten, um beim Thema dieses Kapitels zu bleiben.

python
simple_llm = ChatOpenAI(model="gpt-5.4-mini")
 
def simple_handler(state: State):
    """Beantwortet einfache Anfragen mit einem kleinen Modell."""
    response = simple_llm.invoke(state["messages"])
    return {"messages": [response]}

Der Handler für komplexe Beratungen verwendet ein Hochleistungsmodell. Aus demselben Grund wie beim Handler für einfache Anfragen haben wir ihn einfach gehalten — er erzeugt lediglich eine Antwort.

python
complex_llm = ChatOpenAI(model="gpt-5.4")
 
def complex_handler(state: State):
    """Beantwortet komplexe Beratungen mit einem Hochleistungsmodell."""
    response = complex_llm.invoke(state["messages"])
    return {"messages": [response]}

Der Handler für Bestellabfragen muss ein Bestellabfrage-Tool verwenden, was bedeutet, dass er eine tool-aufrufende Schleife benötigt. Da seine Struktur identisch mit einem standardmäßigen tool-aufrufenden Agent ist, bauen wir ihn mit create_agent.

python
from langchain.tools import tool
from langchain.agents import create_agent
 
@tool
def get_order_status(order_id: str) -> str:
    """Ruft den Lieferstatus einer Bestellung anhand der Bestellnummer ab."""
    fake_data = {"12345": "Unterwegs, morgen erwartet", "67890": "Zugestellt"}
    return fake_data.get(order_id, f"Bestellung {order_id} nicht gefunden.")
 
order_agent = create_agent(
    model="openai:gpt-5.4-mini",
    tools=[get_order_status],
)

16.2.4) Den Graphen zusammensetzen und ausführen

Verbinden wir alle Nodes, die wir gebaut haben, zu einem Graphen. Wir platzieren classify am Startpunkt, verbinden ihn über eine Conditional Edge mit den drei Handlern und konfigurieren jeden Handler so, dass er nach seiner Fertigstellung endet.

python
from langgraph.graph import StateGraph, START, END
 
builder = StateGraph(State)
 
builder.add_node("classify", classify)
builder.add_node("simple", simple_handler)
builder.add_node("complex", complex_handler)
builder.add_node("order", order_agent)      # Den create_agent-Graphen als Node registrieren
 
builder.add_edge(START, "classify")
builder.add_conditional_edges("classify", route_by_intent)
builder.add_edge("simple", END)
builder.add_edge("complex", END)
builder.add_edge("order", END)
 
agent = builder.compile()

Führen wir eine Anfrage jedes Typs aus und sehen wir, welcher Handler sie verarbeitet.

python
from langchain_core.messages import HumanMessage
 
for question in [
    "Wie sind Ihre Öffnungszeiten?",
    "Meine Bestellung kam beschädigt an. Sollte ich einen Umtausch oder eine Rückerstattung bekommen?",
    "Wie ist der Lieferstatus von Bestellung 12345?",
]:
    result = agent.invoke({"messages": [HumanMessage(content=question)]})
    print(f"F: {question}")
    print(f"[{result['intent']}] A: {result['messages'][-1].content}\n")

Ausgabe:

F: Wie sind Ihre Öffnungszeiten?
[simple] A: Ich habe keine festen Öffnungszeiten – ich bin rund um die Uhr verfügbar.
...
 
F: Meine Bestellung kam beschädigt an. Sollte ich einen Umtausch oder eine Rückerstattung bekommen?
[complex] A: Wenn Ihre Bestellung beschädigt ankam, haben Sie im Allgemeinen Anspruch auf einen **Ersatz/Umtausch oder eine vollständige Rückerstattung**.
...
 
F: Wie ist der Lieferstatus von Bestellung 12345?
[order] A: Bestellung 12345 ist **unterwegs** und wird **morgen erwartet**.

Jede Anfrage wurde über einen anderen Pfad verarbeitet. simple_handler beantwortete die einfache Anfrage mit einem kleinen Modell, complex_handler beantwortete die komplexe Beratung mit einem Hochleistungsmodell und order_agent rief das Tool get_order_status auf, um die Bestellabfrage zu beantworten.