Chains sind elegant. Sie nehmen einen Input, leiten ihn durch eine definierte Sequenz von Transformationen, und liefern ein Ergebnis. Jeder Schritt ist vorherbestimmt, die Reihenfolge festgelegt. Ein Chain ist wie eine Fabrikstraße: deterministisch, nachvollziehbar, vorhersagbar.
Aber was passiert, wenn die Aufgabe nicht mehr vorhersagbar ist?
Was, wenn der Pfad zur Lösung erst während der Ausführung klar wird? Wenn die nächste Aktion davon abhängt, was der vorherige Schritt ergeben hat? Wenn das Modell selbst entscheiden muss, welches Werkzeug es als nächstes benötigt – oder ob es überhaupt eines braucht?
Hier endet die Welt der Chains. Hier beginnt die Welt der Agents.
Ein Agent ist kein magischer, autonomer Akteur. Er ist auch keine „KI, die eigenständig denkt”. Diese Vorstellung ist romantisch, aber technisch ungenau. Ein Agent ist ein strukturierter Regelkreis, dessen Ablauf durch LLM-Output gesteuert wird. Die vermeintliche Autonomie ist nichts anderes als ein iterativer Prozess mit expliziten Entscheidungspunkten.
Der Kern eines Agents lässt sich als State Machine beschreiben:
Benutzereingabe → Prompt-Konstruktion → Modellaufruf
↓
Entscheidung
/ \
Tool-Call Finale Antwort
↓ ↓
Tool-Ausführung Output
↓
Ergebnis → Prompt-Erweiterung
↓
Modellaufruf (erneut)
Der Kreislauf schließt sich, solange das Modell Tool-Aufrufe generiert. Erst wenn das Modell signalisiert „Ich bin fertig”, endet die Schleife. Diese Abbruchbedingung ist kritisch. Ohne sie würde der Agent endlos weiterlaufen.
Was unterscheidet einen Agent also technisch von einem Chain? Es sind drei wesentliche Mechanismen:
Das Modell erhält nicht nur die ursprüngliche Aufgabe, sondern auch den bisherigen Verlauf: Was wurde bereits unternommen? Welche Tools wurden aufgerufen? Was haben sie zurückgegeben? Dieser Kontext – oft als Scratchpad bezeichnet – dient als temporärer Arbeitsspeicher. Das Modell kann auf dieser Grundlage „nachdenken”, bevor es die nächste Aktion wählt.
Reasoning ist hier kein mystischer Vorgang. Es ist schlicht die Fähigkeit des Modells, aus dem bisherigen Kontext abzuleiten, welcher Schritt als nächstes sinnvoll ist. Das geschieht durch Prompt-Engineering: Der Agent-Prompt instruiert das Modell explizit, die Situation zu analysieren und eine Entscheidung zu treffen.
Der Agent hat Zugriff auf eine Menge registrierter Tools. Jedes Tool wird dem Modell als strukturierte Beschreibung präsentiert: Name, Parameter, Zweck. Das Modell kann – auf Basis seiner Trainingsdaten und der Prompt-Anweisung – entscheiden, welches Tool für die aktuelle Teilaufgabe geeignet ist.
Tool-Calls sind keine Funktionsaufrufe im klassischen Sinne. Sie sind strukturierte Ausgaben des Modells, die dann vom Agent-Framework geparst und in echte Funktionsaufrufe übersetzt werden. Das Modell generiert beispielsweise eine JSON-Struktur:
{
"tool": "get_weather",
"arguments": {
"location": "Berlin"
}
}Das Framework erkennt diese Struktur, führt die Funktion
get_weather("Berlin") aus und fügt das Ergebnis dem
Scratchpad hinzu. Das Modell erhält daraufhin eine neue Prompt-Iteration
mit der „Observation” – dem Ergebnis des Tool-Calls.
#####Iterative Ausführung und Abbruchbedingung
Ein Agent ist ein Loop. Nach jedem Tool-Call wird das Modell erneut aufgerufen. Es kann weitere Tools verwenden, oder es kann entscheiden, dass genug Informationen vorliegen, um die finale Antwort zu formulieren. Die Abbruchbedingung ist entweder eine explizite Finish-Anweisung des Modells oder eine maximale Anzahl an Iterationen (um Endlosschleifen zu vermeiden).
Diese Struktur macht den Agent flexibel, aber auch teuer. Jeder Loop-Durchlauf bedeutet einen zusätzlichen API-Call. Ein Agent, der zehn Tools nacheinander aufruft, generiert elf Modellaufrufe (plus einen für die finale Antwort).
Agents führen keine globale Planung durch. Sie erstellen keinen Vorausschau-Baum möglicher Aktionen. Sie evaluieren nicht, ob eine Folge von Tool-Calls zum Ziel führt, bevor sie diese ausführen. Ein Agent ist ein greedy executor: Er wählt den nächsten Schritt auf Basis des aktuellen Zustands, ohne zukünftige Schritte im Detail vorauszuplanen.
Das bedeutet auch: Ein Agent kann scheitern. Er kann in eine Sackgasse laufen, redundante Tools mehrfach aufrufen oder unnötige Zwischenschritte einfügen. Aktuelle Agents sind nicht perfekt. Sie sind iterative Approximatoren, keine Optimierer.
Diese Limitierung ist wichtig zu verstehen. Wer Agents als „vollautonome Problemlöser” versteht, wird enttäuscht. Wer sie als „orchestrierte Tool-Nutzer mit iterativer Entscheidungsfindung” versteht, kann sie effektiv einsetzen.
| Dimension | Chain | Agent |
|---|---|---|
| Ablauf | Sequenziell, festgelegt | Iterativ, dynamisch |
| Entscheidungsfindung | Vordefiniert | Modellbasiert |
| Tool-Nutzung | Statisch eingebunden | Dynamisch gewählt |
| Komplexität | Linear | Zyklisch |
| Kontrollfluss | Deterministisch | Nondeterministisch |
| Einsatzgebiet | Bekannte Abläufe | Unvorhersehbare Aufgaben |
Ein Chain ist wie eine Schablone. Ein Agent ist wie ein Assistent, der improvisiert.
Die moderne LangChain-API (v1.0) vereinfacht die Agent-Erstellung
erheblich. Die zentrale Funktion heißt create_agent(). Sie
nimmt drei wesentliche Parameter: ein Modell, eine Liste von Tools und
optional Middleware für erweiterte Steuerung.
Beginnen wir mit einem minimalen Beispiel. Wir bauen einen Agent, der Zugriff auf zwei einfache Tools hat: eine Wetterabfrage und eine Stringmanipulation. Die Aufgabe des Agents wird sein, auf Basis der Benutzereingabe zu entscheiden, welches Tool er wann benötigt.
Tools werden in LangChain als Funktionen mit Typisierung und
Docstring definiert. Der @tool-Decorator kümmert sich um
die Registrierung und Metadaten-Extraktion:
from langchain.tools import tool
@tool
def get_weather(location: str) -> str:
"""
Ruft Wetterinformationen für einen gegebenen Ort ab.
Args:
location: Der Name der Stadt oder Region (z.B. "Berlin", "München")
Returns:
Eine lesbare Beschreibung der aktuellen Wetterlage.
"""
# In einer realen Anwendung würde hier ein API-Aufruf stehen
weather_data = {
"Berlin": "Bewölkt, 12°C, leichter Wind aus West",
"München": "Sonnig, 18°C, windstill",
"Hamburg": "Regnerisch, 9°C, starker Wind aus Nord"
}
return weather_data.get(location, f"Keine Wetterdaten für {location} verfügbar")
@tool
def reverse_string(text: str) -> str:
"""
Kehrt einen Text um.
Args:
text: Der umzukehrende String
Returns:
Der rückwärts geschriebene Text.
"""
return text[::-1]Die Docstrings sind essentiell. Sie werden dem Modell als Beschreibung präsentiert. Das Modell entscheidet anhand dieser Beschreibung, ob ein Tool relevant ist. Ein vager Docstring führt zu schlechten Tool-Auswahlentscheidungen.
Die Typisierung (location: str, -> str)
ist ebenfalls wichtig. Sie wird für die Schema-Generierung genutzt und
hilft dem Modell, die erwarteten Parameter zu verstehen.
Jetzt setzen wir die Komponenten zusammen. Ein vollständiges, ausführbares Beispiel sieht so aus:
import os
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain.tools import tool
# API-Key setzen (in realen Projekten: Umgebungsvariablen verwenden)
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
# Tool-Definitionen
@tool
def get_weather(location: str) -> str:
"""
Ruft Wetterinformationen für einen gegebenen Ort ab.
Args:
location: Der Name der Stadt oder Region
Returns:
Eine lesbare Beschreibung der aktuellen Wetterlage.
"""
weather_data = {
"Berlin": "Bewölkt, 12°C, leichter Wind aus West",
"München": "Sonnig, 18°C, windstill",
"Hamburg": "Regnerisch, 9°C, starker Wind aus Nord"
}
return weather_data.get(location, f"Keine Wetterdaten für {location} verfügbar")
@tool
def reverse_string(text: str) -> str:
"""
Kehrt einen Text um.
Args:
text: Der umzukehrende String
Returns:
Der rückwärts geschriebene Text.
"""
return text[::-1]
# Modell initialisieren
model = ChatOpenAI(
model="gpt-4o-mini",
temperature=0 # Deterministische Ausgaben für reproduzierbare Ergebnisse
)
# Agent erstellen
agent = create_agent(
model=model,
tools=[get_weather, reverse_string]
)
# Agent aufrufen
if __name__ == "__main__":
# Beispiel 1: Tool-Nutzung erforderlich
result = agent.invoke({
"messages": [{"role": "user", "content": "Wie ist das Wetter in Berlin?"}]
})
print("=== Beispiel 1: Wetterabfrage ===")
print(result["messages"][-1].content)
print()
# Beispiel 2: Kombinierte Tool-Nutzung
result = agent.invoke({
"messages": [{"role": "user", "content": "Drehe den String 'LangChain' um und sag mir dann, ob das Wetter in München gut ist."}]
})
print("=== Beispiel 2: Mehrfache Tool-Aufrufe ===")
print(result["messages"][-1].content)
print()
# Beispiel 3: Keine Tools notwendig
result = agent.invoke({
"messages": [{"role": "user", "content": "Was ist 2 + 2?"}]
})
print("=== Beispiel 3: Direkte Antwort ohne Tools ===")
print(result["messages"][-1].content)Wenn der Agent die erste Anfrage erhält („Wie ist das Wetter in Berlin?“), passiert Folgendes:
Prompt-Konstruktion: Das Framework baut einen Prompt, der die Benutzernachricht, die verfügbaren Tools (als strukturierte Beschreibungen) und Anweisungen zur Tool-Nutzung enthält.
Modellaufruf: Das Modell wird mit diesem Prompt
aufgerufen. Es analysiert die Anfrage und erkennt, dass es das Tool
get_weather benötigt.
Tool-Call-Generierung: Das Modell gibt eine strukturierte Antwort zurück, die den Tool-Namen und die Parameter enthält:
{
"name": "get_weather",
"arguments": {"location": "Berlin"}
}Tool-Ausführung: Das Framework parst diese
Ausgabe, führt get_weather("Berlin") aus und erhält die
Rückgabe.
Erneuter Modellaufruf: Der ursprüngliche Prompt wird erweitert. Jetzt enthält er:
Finale Antwort: Das Modell erhält den erweiterten Kontext und formuliert die Endantwort: „In Berlin ist es bewölkt, 12°C, mit leichtem Wind aus West.”
Die gesamte Loop-Struktur ist im Framework gekapselt. Als Entwickler:in müssen wir sie nicht explizit programmieren.
Im zweiten Beispiel fordert der Benutzer zwei verschiedene Aktionen: String-Umkehrung und Wetterabfrage. Der Agent führt diese sequenziell aus:
reverse_string("LangChain") → Ergebnis:
„niahCgnaL”get_weather("München") → Ergebnis:
„Sonnig, 18°C, windstill”Dies demonstriert die zentrale Eigenschaft von Agents: Sie können Tools in Abhängigkeit voneinander aufrufen. Der zweite Tool-Call erfolgt erst, nachdem der erste abgeschlossen ist und das Ergebnis im Scratchpad verfügbar ist.
In produktionsnahen Systemen ist Nachvollziehbarkeit entscheidend. Wie lässt sich sehen, was der Agent tut? Der Stream-Modus gibt Einblick in den Ausführungsverlauf:
print("=== Stream-Modus: Einblick in die Agent-Ausführung ===")
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Wie ist das Wetter in Hamburg?"}]},
stream_mode="values"
):
latest_message = chunk["messages"][-1]
if hasattr(latest_message, 'content') and latest_message.content:
print(f"[Agent]: {latest_message.content}")
elif hasattr(latest_message, 'tool_calls') and latest_message.tool_calls:
for tool_call in latest_message.tool_calls:
print(f"[Tool-Call]: {tool_call['name']} mit Argumenten {tool_call['args']}")Dieser Code gibt Schritt für Schritt aus, was der Agent tut: Welche Tools er aufruft, welche Argumente er übergibt, und wie die finale Antwort lautet. Für Debugging und Monitoring ist dieser Einblick unverzichtbar.
Ein Agent ist kein Allheilmittel. Hier einige typische Probleme:
Redundante Tool-Calls: Das Modell kann dasselbe Tool mehrfach mit denselben Parametern aufrufen, wenn es die vorherigen Ergebnisse „vergisst” oder falsch interpretiert.
Schlechte Tool-Beschreibungen: Vage oder unvollständige Docstrings führen zu falschen Tool-Auswahlentscheidungen. Das Modell kann nur so gut wählen, wie die Beschreibung es zulässt.
Fehlende Abbruchbedingung: Ohne maximale Iterationsanzahl kann ein Agent in eine Endlosschleife geraten. LangChain setzt standardmäßig ein Limit, aber dieses sollte an die Aufgabe angepasst werden.
Kostenexplosion: Jeder Loop-Durchlauf kostet API-Tokens. Ein Agent, der zehn Schritte benötigt, verursacht zehnmal so viel Aufwand wie ein einzelner Modellaufruf.
Die v1.0-API bietet Middleware-Mechanismen, um den Agent-Ablauf anzupassen. Middleware erlaubt es, vor oder nach dem Modellaufruf in den Prozess einzugreifen:
from langchain.agents.middleware import before_model, ModelRequest
@before_model
def inject_context(request: ModelRequest):
"""Fügt zusätzlichen Kontext vor jedem Modellaufruf hinzu."""
current_messages = request.state["messages"]
# Beispiel: System-Nachricht hinzufügen
if not any(msg.get("role") == "system" for msg in current_messages):
request.state["messages"].insert(0, {
"role": "system",
"content": "Du bist ein hilfreicher Assistent. Antworte präzise und nutze Tools, wenn nötig."
})
return None # State-Modifikationen erfolgen direkt
agent_with_middleware = create_agent(
model=model,
tools=[get_weather, reverse_string],
middleware=[inject_context]
)Middleware ist mächtig. Sie erlaubt Message-Trimming (um Token-Limits einzuhalten), Content-Filtering, dynamische Modellauswahl und vieles mehr. Für produktionsreife Systeme ist Middleware oft unverzichtbar.
Manchmal soll der Agent nicht nur eine Textantwort liefern, sondern
strukturierte Daten. Die response_format-Option ermöglicht
dies:
from pydantic import BaseModel
from langchain.agents.structured_output import ToolStrategy
class WeatherReport(BaseModel):
"""Strukturierter Wetterbericht."""
location: str
temperature: int
condition: str
wind: str
agent_structured = create_agent(
model=model,
tools=[get_weather],
response_format=ToolStrategy(WeatherReport)
)
result = agent_structured.invoke({
"messages": [{"role": "user", "content": "Gib mir einen strukturierten Wetterbericht für Berlin."}]
})
# Zugriff auf strukturierte Ausgabe
weather_report = result["structured_response"]
print(f"Temperatur: {weather_report.temperature}°C")
print(f"Bedingungen: {weather_report.condition}")Dies kombiniert die Flexibilität von Agents mit der Vorhersagbarkeit strukturierter Outputs. Ideal für Anwendungen, die weiterverarbeitbare Daten benötigen.
Ein Agent ist keine magische KI. Er ist eine Orchestrierungsschicht, die ein LLM befähigt, iterativ Tools zu nutzen und auf Basis von Zwischenergebnissen zu entscheiden. Die vermeintliche Autonomie entsteht durch geschicktes Prompt-Engineering, strukturierte Tool-Beschreibungen und eine Loop-basierte Architektur.
Die Stärke von Agents liegt in ihrer Flexibilität. Sie können Aufgaben lösen, deren Ablauf nicht vorhersehbar ist. Die Schwäche liegt in ihrer Komplexität: Mehr API-Calls, höhere Kosten, schwieriger zu debuggen.
Wer Agents einsetzt, sollte verstehen, was sie sind und was sie nicht sind. Sie sind keine Optimierer. Sie sind keine globalen Planer. Sie sind iterative Entscheider.
Ich lese zunächst die hochgeladene Datei, um das aktuelle Material zu verstehen und meine Darstellung darauf aufzubauen.Perfekt, ich habe das Material analysiert. Jetzt schreibe ich den Text über Memory in LangChain, basierend auf der modernen LangChain v1.0 API und den Konzepten aus dem hochgeladenen Dokument.