Startseite / Artikel

Erstellen Sie einen Mehr-Agenten-Router mit LangGraph und einem Überwachungsagenten

Wie man einen überlasteten Agenten in Spezialisten mit einem Supervisor in LangGraph aufteilt, unter Verwendung eines vorgefertigten Hilfsprogramms und eines manuell erstellten StateGraphs, sowie Hinweise zur Zustandsgestaltung und zu häufigen Fehlern bei Schleifen.

Verlag: REACTAPP.TOP9 Min. Lesezeit2122 Wörter

Quellen: Build a Multi-Agent Router with LangGraph in 30 Minutes

Setzt man einen einzigen Agenten vor ein echtes Produkt, beginnt er bald, zu viel zu tun. Bittet man einen Agenten um die Abwicklung von Kundenservice, Bestellverfolgung und Rückerstattungen, ist er in allen drei Bereichen mittelmäßig. Seine Systemanweisungen entwickeln sich zu einem 2.000 Wörter langen Wirrwarr aus „Wenn der Benutzer nach X fragt, dann macht Y – aber wenn es auch um Z geht, dann...“. Die Lösung liegt nicht in einem klügeren Agenten, sondern in mehreren kleineren Agenten zusammen mit einem Supervisor, der zwischen ihnen leitet. Dies ist wohl das am weitesten verbreitete Produktionsmuster in der agentbasierten KI derzeit, und eine funktionierende Version passt in ein kurzes LangGraph-Programm.

Das Problem mit einem riesigen Agenten

Nehmen wir an, man entwickelt einen Assistenten, der allgemeine Fragen beantwortet, Arithmetikaufgaben löst und im Internet nach aktuellen Fakten sucht. Der verlockende Ansatz besteht darin, einen einzigen Agenten zu verwenden, einen großen Prompt sowie alle Tools direkt daran anzuhängen. Wenn sich jedoch immer mehr Tools und Anweisungen anhäufen, wird das Modell unsicher, welches Tool es wählen soll, und seine Argumentation verliert an Klarheit. Das Debuggen verwandelt sich dabei in einen Albtraum, da man nicht herausfinden kann, warum das Modell das falsche Tool ausgewählt hat, wenn alles in einem einzigen Prompt enthalten ist.

Die Lösung besteht darin, die Verantwortlichkeiten aufzuteilen: einen Agenten für Mathematik, einen für Recherche sowie einen Supervisor, der lediglich als Router fungiert und entscheidet, welcher Spezialist welche Aufgabe in einer Anfrage übernimmt.

Das Supervisor-Muster

Stellen Sie sich ein Callcenter vor. Sie rufen an mit einer Frage und werden nicht direkt zum Experten durchgestellt. Zuerst hört ein Empfangsmitarbeiter zu, entscheidet, ob Sie eine Rechnungsstellung, technische Unterstützung oder Verkauf benötigen, und leitet Sie weiter. Der Empfangsmitarbeiter ist der Supervisor und die drei Teams bestehen aus den Spezialisten. Der Supervisor beantwortet niemals die Frage selbst; seine einzige Aufgabe ist es, auszuwählen, wer das tun soll.

Visuell sieht es so aus:

                    ┌─────────────┐
                    │    User     │
                    │   Request   │
                    └──────┬──────┘
                           │
                           ▼
                   ┌───────────────┐
                   │   Supervisor  │
                   │  (routes task)│
                   └───────┬───────┘
                    ┌──────┴──────┐
                    ▼             ▼
             ┌────────────┐ ┌────────────┐
             │  Research  │ │    Math    │
             │   Agent    │ │   Agent    │
             └──────┬─────┘ └──────┬─────┘
                    │              │
                    └──────┬───────┘
                           ▼
                   ┌───────────────┐
                   │   Supervisor  │
                   │ (final answer)│
                   └───────────────┘

Der Supervisor übernimmt die Aufgabe, leitet sie an den richtigen Spezialisten weiter, erhält das Ergebnis und entscheidet anschließend, ob sie erneut weitergeleitet oder dem Benutzer die endgültige Antwort gegeben werden soll.

Die empfohlene Implementierung, Schritt für Schritt

Ein Punkt, den man vor dem Schreiben von Code kennen sollte: Das LangChain-Team empfiehlt nun, das Supervisor-Muster direkt über die Aufrufung von Tools zu implementieren, anstatt sich ausschließlich auf die ältere langgraph-supervisor-Hilfsbibliothek zu verlassen, da der Toolaufruf mehr Kontrolle über die Gestaltung des Kontexts bietet. Unten finden Sie zunächst die vorgefertigte Version und danach eine manuelle, damit klar wird, was im Hintergrund abläuft.

Schritt 1: Die Pakete installieren

pip install langgraph langchain-openai langgraph-supervisor

Schritt 2: Die Spezialisten erstellen

Fangen Sie mit den Importen an:

from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent

Der erste Spezialist kümmert sich um Mathematik, und seine Werkzeuge sind zwei kleine Funktionen. Zunächst kommt die Addition:

# Specialist 1: handles math
def add(a: float, b: float) -> float:
    """Add two numbers together."""
    return a + b

Dann die Multiplikation:

def multiply(a: float, b: float) -> float:
    """Multiply two numbers together."""
    return a * b

Umhüllen Sie sie mit einem ReAct-Agenten, der einen Namen sowie eine Anweisung hat, die seine Rolle einschränkt:

math_agent = create_react_agent(
    model="openai:gpt-4o",
    tools=[add, multiply],
    name="math_expert",
    prompt="You are a math specialist. Solve numeric problems precisely."
)

Der zweite Spezialist führt Forschungen durch. Ein seiner Werkzeuge ist eine Suchfunktion, die hier vereinfacht dargestellt wird:

# Specialist 2: handles research
def web_search(query: str) -> str:
    """Search the web for current information."""
    # In production, replace this with a real search API call
    return f"Search results for '{query}': [your real search integration here]"

Umhüllen Sie das auch mit einem Agenten:

research_agent = create_react_agent(
    model="openai:gpt-4o",
    tools=[web_search],
    name="research_expert",
    prompt="You are a research specialist. Find accurate, current information."
)

Jeder Agent hat genau eine Aufgabe sowie nur die Werkzeuge, die für diese Aufgabe erforderlich sind, sodass es weder zu Verwirrung noch zu überlasteten Anfragen kommt. Die Beispiele verwenden OpenAI-Modelle, obwohl auch kostenlose Alternativen wie Groq oder OpenRouter funktionieren würden.

Schritt 3: Den Supervisor erstellen

Importieren Sie den Hilfsprogramm:

from langgraph_supervisor import create_supervisor

Geben Sie ihm dann die beiden Spezialisten, ein Modell sowie eine Anweisung, die angibt, wann jedes verwendet werden soll:

workflow = create_supervisor(
    [research_agent, math_agent],
    model=ChatOpenAI(model="gpt-4o"),
    prompt=(
        "You are a team supervisor managing a research expert and a math expert. "
        "For questions requiring current information or web lookups, use research_expert. "
        "For anything involving calculation, use math_expert. "
        "Route each request to exactly the right specialist."
    )
)

Übersetzen Sie den Workflow in eine ausführbare Anwendung:

app = workflow.compile()

Schritt 4: Ausführen

Senden Sie eine Mathematikaufgabe:

result = app.invoke({
    "messages": [
        {"role": "user", "content": "What's 1,847 multiplied by 293?"}
    ]
})

Und geben Sie die endgültige Nachricht aus:

print(result["messages"][-1].content)

So verläuft der Prozess im Hintergrund:

  1. Der Supervisor liest die Anfrage.
  2. Er erkennt, dass es sich um eine Berechnung und nicht um eine Forschungsfrage handelt.
  3. Er leitet die Aufgabe an math_expert weiter.
  4. math_expert berechnet die Antwort mithilfe seines multiply-Tools.
  5. Der Supervisor übernimmt wieder die Kontrolle und gibt die Antwort zurück.

Für Forschungsfragen, wie zum Beispiel die aktuellen Mitarbeiterzahlen großer Technologieunternehmen, leitet derselbe Supervisor die Anfrage an research_expert weiter. Der Code bleibt identisch, nur die Entscheidung zur Weiterleitung unterscheidet sich, und die LLM trifft diese Entscheidung selbstständig.

Was die Weiterleitungswerkzeuge tatsächlich sind

Dieser Teil beseitigt das Gefühl von Magie. Der Supervisor leitet die Anfragen nicht über irgendein verborgenes LangGraph-Verfahren, sondern über Tool-Aufrufe – genauso wie jeder gewöhnliche Agent, der Tools verwendet. Im Hintergrund erstellt create_supervisor für jeden Spezialisten ein Übertragungstool mit Namen wie transfer_to_research_expert und transfer_to_math_expert. Das Supervisor-Modell betrachtet diese als aufrufbare Tools, die sich nicht von einer Wetterabfrage unterscheiden; daher ruft es bei der Erkenntnis, dass Forschung erforderlich ist, einfach die Übertragungsfunktion auf. Die Folge ist beruhigend: Ein Supervisor ist keineswegs ein neues Konzept – es handelt sich um den üblichen Agentenzyklus, bei dem einige der Tools zufällig andere Agenten statt API-Aufrufe sind.

Eigenhändige Erstellung

Falls Sie volle Kontrolle über das Routing wünschen – was bei der Vorbereitung auf die Produktion ratsam ist – finden Sie hier die manuelle Version zusätzlich zu einem rohen StateGraph. Beginnen Sie mit den Imports:

from typing import Annotated, Sequence, TypedDict
import operator
from langchain_core.messages import BaseMessage
from langgraph.graph import StateGraph, END

Definieren Sie den gemeinsamen Zustand, der die Nachrichtenliste sowie den Namen des nächsten Knotens enthält:

class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    next: str

Der Überwachungsknoten prüft die neueste Nachricht:

def supervisor_node(state: AgentState):
    """Decide which specialist should act next."""
    last_message = state["messages"][-1].content

In der Produktion würde die Entscheidung aus einem Aufruf eines LLM stammen. Hier wird es auf einfache Schlüsselwortprüfungen reduziert, damit die Routing-Logik sichtbar bleibt:

# In production, this decision comes from an LLM call —
    # simplified here to show the routing logic clearly
    if any(word in last_message.lower() for word in ["calculate", "multiply", "add", "sum"]):
        return {"next": "math_agent"}
    else:
        return {"next": "research_agent"}

Jeder Spezialist erhält einen Knoten, der seinen Agenten aufruft und anschließend die Steuerung an den Supervisor zurückgibt. Zuerst Mathematik:

def math_node(state: AgentState):
    result = math_agent.invoke(state)
    return {"messages": [result], "next": "supervisor"}

Dann recherchieren:

def research_node(state: AgentState):
    result = research_agent.invoke(state)
    return {"messages": [result], "next": "supervisor"}

Dann verbinden Sie das Diagramm, indem Sie die Knoten registrieren:

# Wire the graph together
workflow = StateGraph(AgentState)
workflow.add_node("supervisor", supervisor_node)
workflow.add_node("math_agent", math_node)
workflow.add_node("research_agent", research_node)

Legen Sie den Eingangspunkt fest:

workflow.set_entry_point("supervisor")

Fügen Sie die bedingten Kanten hinzu, die der Entscheidung des Vorgesetzten folgen:

workflow.add_conditional_edges(
    "supervisor",
    lambda state: state["next"],
    {"math_agent": "math_agent", "research_agent": "research_agent", "end": END}
)

Schicken Sie nachher jeden Spezialisten zurück zum Vorgesetzten:

workflow.add_edge("math_agent", "supervisor")
workflow.add_edge("research_agent", "supervisor")

Und kompilieren:

app = workflow.compile()

Es ist ausführlicher, aber nichts bleibt verborgen. Man kann sehen, wie der Zustand fließt, wie jede Routing-Entscheidung getroffen wird und wo man eigene Logik hinzufügen kann – wie zum Beispiel Logging, Schutzmechanismen oder benutzerdefinierte Beendigungsbedingungen.

Die schlechteste Wahl bei der Zustandsarchitektur im Laufe der Zeit

Eine Warnung aus der Praxis: Das Zustandschema ist die schwierigste Entscheidung, die man später noch ändern kann. Es ist verlockend, bereits zu Beginn alle möglichen Felder in ein einziges, flaches Dictionary aufzunehmen – inklusive Nachrichten, Benutzerkontext und Zwischenergebnisse. Das funktioniert bei zwei Agenten noch. Bei acht oder zehn Knoten, die alle denselben gemeinsamen Zustand lesen und schreiben, wird es jedoch problematisch, denn LangGraphs Typüberprüfung ist streng, und um einen überdimensionierten Zustand zu entwirren, muss man jeden Knoten anfassen. Entwerfen Sie das Schema von Anfang an bewusst – selbst für ein System mit zwei Agenten. Entscheiden Sie, was jeder Agent tatsächlich lesen und schreiben muss, und halten Sie alles andere außerhalb des gemeinsamen Zustands.

Eine Fehlerart, der Sie begegnen werden

Supervisionsysteme, die in die Produktion kommen, weisen oft dieses Muster auf. Stellen Sie sich vor, der Supervisor delegiert eine Aufgabe. Der Spezialist erledigt sie, doch der Supervisor befundet das Ergebnis als unzureichend und schickt es zurück. Der Spezialist überarbeitet es, und die neue Version ist tatsächlich noch schlechter als die erste. Der Supervisor ist weiterhin unzufrieden und schickt es erneut zurück – so kann das endlos weitergehen, es sei denn, man schützt sich davor.

Die Lösung besteht darin, eine Obergrenze für die Rücksendungen durch den Supervisor festzulegen, was durch einen einfachen Zähler im Zustand sowie eine klare, deterministische Abbruchbedingung erreicht werden kann. Die Meinung eines LLM darüber, ob etwas gut genug ist, stellt keine zuverlässige alleinige Abbruchregel dar.

Wann man es nicht verwenden sollte

Seyn Sie ehrlich bezüglich der Komplexität. Wenn die Aufgabe darin besteht, einmal Daten abzurufen und anschließend etwas zu generieren, wie bei einer einfachen RAG-basierten Frage-Antwort-Funktion, ist ein Supervisor überflüssig. Die zusätzliche Verwaltung von Zuständen und die damit verbundenen Routing-Kosten bringen nichts, da es keine echte Entscheidung zur Weiterleitung gibt. Wenden Sie das Supervisor-Muster erst an, wenn Sie wirklich unterschiedliche Spezialisten sowie eine echte Wahl bezüglich der Weiterleitung haben – nehmen Sie es nicht standardmäßig an.

Zusammenfassung

  • Ein überlasteter Agent versagt, wenn die Komplexität steigt.
  • Ein Supervisor ist ein Router-Agent, der Aufgaben an Spezialisten delegiert.
  • create_supervisor ist der schnelle, vorgefertigte Routing-Weg.
  • Im Grunde sind die Übertragungen gewöhnliche Tool-Aufrufe.
  • Ein manueller StateGraph gewährleistet vollständige Kontrolle über das Routing.
  • Zwei Lektionen aus der Praxis: Entwerfen Sie das Zustandsschema frühzeitig und begrenzen Sie die Revisionsschleifen.