Reasoning Loops von AI Agents: ReAct, ReWOO, Plan-and-Execute
Automatische Übersetzung Dieser Artikel wurde automatisch aus der englischen Originalversion übersetzt.
Artikel-Update
Ursprünglich am 31. Januar 2026 veröffentlicht. Am 6. September 2026 geprüft und aktualisiert. Das Update konzentriert sich auf neuere Model-Fähigkeiten und Reasoning-APIs und enthält überarbeitete Beispiele sowie Quellenlinks.
Ein Reasoning Loop eines Agents ist der Kontrollfluss, der entscheidet, wann ein Model plant, ein Tool aufruft, das Ergebnis liest und stoppt. Für einen Engineer, der einen Agent entwickelt, ist diese Entscheidung zugleich ein Budget: Sie bestimmt, wie oft das Model ausgeführt wird, wie viel Historie jeder Aufruf mitführt und ob ein überraschendes Ergebnis die nächste Aktion verändern kann.
Dieser Artikel vergleicht ReAct, ReWOO und Plan-and-Execute anhand eines LangGraph Market Analyst Agents, den ich entwickelt habe. Am Ende verfügen Sie über eine Routing-Regel und anpassbare Implementierungsstrukturen – nicht nur über drei Namen, die Sie einem Diagramm hinzufügen können.
Der Loop ist die innerste Schicht dieser Serie. Memory, Tools, Security, Runtime und Checks, bevor eine Aufgabe als abgeschlossen gilt, umgeben ihn; sie ersetzen ihn nicht.
Den kurzen Framework-Vergleich finden Sie unter Best AI Agent Frameworks in 2026.
Der Reasoning Loop entscheidet, was als Nächstes zu tun ist. Er speichert keinen State, führt keine Tools aus und autorisiert keine Side Effects.
Das Harness ist das Steuerungsprogramm zwischen Model und Maschine. Es stellt Prompts aus gespeichertem State zusammen (Teil 2), definiert die Aktionen, die das Model benennen darf (Teil 3), autorisiert Calls (Teil 4) und prüft die Evidenz, bevor es eine Aufgabe als abgeschlossen erklärt (Teil 6). Das sind getrennte Engineering-Probleme, aber ein Turn durchläuft alle vier.
Die Runtime (Teil 5) stellt das Session-Log, die Sandbox, den Checkpoint Store und die Traces bereit, die über den Lebenszyklus eines einzelnen Worker-Prozesses hinaus bestehen bleiben.
Jeder Beitrag steht für sich. Zusammen bewegen sie sich vom Loop nach außen.
Mit der Fehlergrenze beginnen
Ein guter Prompt beantwortet die Frage nach dem Kontrollfluss nicht. Die Patterns unterscheiden sich darin, wie viel Arbeit vor dem ersten Tool Call festgelegt wird. Das bestimmt die Anzahl der Model-Aufrufe, wann ein fehlerhafter Plan sichtbar wird und ob ein unerwartetes Tool-Ergebnis den Run umleiten kann.
Drei Reasoning-Patterns für AI Agents
ReAct: Nach jeder Observation entscheiden
ReAct (Yao et al., 2022), kurz für Reason + Act, hält die nächste Entscheidung nahe an der neuesten Observation:
Der Originalartikel verwendet expliziten Text für Thought, Action und Observation. Moderne native Tool-APIs stellen vorgeschlagene Tool Calls und Tool Results bereit; ein sichtbarer Thought ist nicht erforderlich. Interleaved Thinking ist eine separate Model-/API-Funktion. Im historischen Prompting-Muster gilt:
- Thought: Der Agent erzeugt einen „Thought“, um das Ziel zu zerlegen und den nächsten Schritt zu planen.
- Action: Auf Grundlage des Thoughts ruft er ein Tool auf.
- Observation: Der Agent liest das Ergebnis, das sein Verständnis für den nächsten Thought aktualisiert.
Das verleiht ReAct seine nützlichen Eigenschaften:
- Im manuellen PaLM-540B-HotpotQA-Beispiel des Papers lieferten Wikipedia-Observations weniger halluzinierte Fakten als Chain-of-Thought-Prompting.
- Der Agent kann seine Strategie spontan anhand dessen ändern, was er gerade gesehen hat.
- Die Historie von Tool Calls und Observations liefert einen konkreten Execution Trace.
Der gleiche Loop verursacht auch Kosten:
- Bei einer naiven Full-History-Implementierung ohne Caching verarbeitet jeder Turn die wachsende Historie erneut. Prompt Caching verändert Input-Kosten und Verarbeitungszeit, aber weder die Belegung des Context Window noch veraltete Observations. Miss gecachte und nicht gecachte Tokens separat; Summarization und Truncation verwerfen Context.
- Ineffizient, wenn die Tool Calls im Voraus geplant werden könnten – genau diese Nische besetzt ReWOO.
- Ohne Stop-Bedingung oder Step-Limit kann der Loop unbegrenzt laufen.
Verwende ihn für explorative Aufgaben, Debugging und Arbeiten, bei denen sich die nächste Action nicht vorhersagen lässt.
ReWOO: Den Tool-Graph zuerst kompilieren
ReWOO (Reasoning WithOut Observation) trennt Planning und Execution. Der Planner schreibt die vollständige Tool-Sequenz in einem Durchlauf und verwendet Platzhalter für Werte, die erst nach der Ausführung existieren.
- Plan: Ein LLM Call schreibt den vollständigen Plan der Tool Calls und verwendet Variablen-Platzhalter (
#E1,#E2) für Outputs, die noch nicht existieren. - Worker: Ein Non-LLM-Executor führt die geplanten Tools aus und füllt die Platzhalter. Der Worker des Papers folgt dem Plan; die spätere Implementierung in diesem Artikel ergänzt dependency-aware Parallel Batches für bereite Schritte.
- Solver: Ein abschließender LLM Call übernimmt die gesammelten Observations und schreibt die Antwort.
Diese Trennung bietet:
- Weniger wiederholte Model Calls als ReAct, wenn der initiale Plan gültig bleibt.
- Weniger wiederholte Prompt-History als bei einem verschachtelten Full-History-Loop. Die Tool-Latenz hängt weiterhin davon ab, wie der Worker die Calls einplant.
- Der Planner kann unabhängig und ohne Live-Umgebung Fine-Tuning erhalten.
Sie erzeugt jedoch eine harte Grenze. Im HotpotQA-Stresstest des Papers gab jedes Tool No evidence found zurück; ReWOO verlor weniger Genauigkeit als ReAct, weil die fehlgeschlagenen Observations seinen Planner nicht in einen weiteren Loop schickten. Das ist relative Robustheit, aber keine Execution-Recovery-Policy. Eine Implementierung muss weiterhin entscheiden, ob ein Tool-Fehler zu Solver-Evidenz wird, einen Retry auslöst oder den Run abbricht. ReWOO eignet sich für vorhersehbare Workflows; bei einem fehlerhaften initialen Graph replanned es nicht selbstständig.
Verwende es für schnelle Snapshots, Status-Checks und Dashboards mit vorhersehbarem Tool-Verhalten.
Plan-and-Execute: erst zerlegen, dann lokal reagieren
Plan-and-Solve Prompting beschreibt eine Prompting-Methode, die zunächst einen Plan erstellt und anschließend die Subtasks löst. Ein verwandtes Muster für Tool-Orchestration wird häufig als Plan-and-Execute bezeichnet. Der Plan-and-Execute-Leitfaden von LangChain dokumentiert dieses Muster:
- Planungsphase: Der Agent generiert zunächst einen Plan, der die Aufgabe in kleinere Subtasks zerlegt.
- Ausführungsphase: Anschließend führt der Agent diese Subtasks nacheinander aus. Sobald Tools beteiligt sind, läuft jeder Subtask üblicherweise als eigener kleiner ReAct Loop, sodass der Executor weiterhin auf die Rückgabe eines Tools reagieren kann, obwohl der übergeordnete Plan feststeht.
Das ursprüngliche Paper konzentrierte sich auf Zero-Shot Prompting. In einer Tool-Using-Implementierung kann das Orchestration-Muster die geplanten Schritte sequenziell ausführen und unterschiedliche Models für Planung und Ausführung verwenden. Diese Aufteilung der Models ist eine Implementierungsentscheidung und kein durch das Plan-and-Solve-Paper belegtes Ergebnis.
Die Abbildung enthält einen optionalen Replanning-Zweig. Der später in diesem Artikel dargestellte Lehr-Graph verwendet ihn nicht: Feedback kann die Arbeit innerhalb eines einzelnen Schritts ändern, aber nicht den verbleibenden Plan.
Das Muster ist nützlich, weil es Folgendes bietet:
- Hierarchisches Reasoning, das nachbildet, wie ein menschlicher Experte ein Projekt zerlegt.
- Eine explizite Replanning-Kante kann nach einem unerwarteten Ergebnis eines Schritts pausieren und eine Neubewertung ermöglichen.
- Model-Spezialisierung. Der Planner kann teuer, der Executor hingegen günstig sein.
- Mit konfiguriertem Checkpointer kann jeder abgeschlossene Schritt zu einer Resume-Grenze werden.
Die Kosten sind:
- Mehr Model-Round-Trips als bei ReWOO, wenn jeder Schritt seinen eigenen ReAct Loop enthält.
- Mehr zu verwaltender State.
- Overkill für One-Shot-Queries.
Verwenden Sie es für komplexe Analysen und Research-Aufgaben, die eine abschließende Synthese benötigen.
Entscheide danach, wo der Plan scheitern kann
| Merkmal | ReAct (2022) | Plan-and-Execute (2023) | ReWOO (2023) |
|---|---|---|---|
| Kernphilosophie | Improvisator: entscheidet den nächsten Schritt anhand des letzten Ergebnisses, jeweils einen Call nach dem anderen. | Architekt: erstellt einen vollständigen Blueprint, führt ihn aus und überprüft ihn anschließend. | Optimierer: kompiliert einen Dependency Graph und batcht anschließend die bereiten Calls. |
| Workflow | Iterativer Loop: Thought → Action → Observation. | Zweistufig: Phase 1 (Planning), Phase 2 (Execution). | Entkoppelt: Der Planner schreibt einen Graphen aus Tool Calls; der Worker führt sie aus; der Solver setzt die Antwort zusammen. |
| Anpassungsfähigkeit | Am höchsten: kann nach jedem einzelnen Tool Call die Richtung ändern. | Jeder Schritt kann auf sein Ergebnis reagieren. Ein optionaler Replanner kann spätere Schritte überarbeiten. | Am niedrigsten: Das Skript des Planners läuft bis zum Ende; während der Ausführung findet kein Re-Planning statt. |
| Effizienz | Ein naiver Loop mit vollständiger History wiederholt mehr Input-Tokens; Context Management kann dieses Wachstum begrenzen. | Der ReAct Loop jedes Schritts kann mit einem kurzen Context statt mit der History des gesamten Laufs beginnen; ein Replanner fügt bei Aktivierung zusätzliche Planning Calls hinzu. | Weniger Model Calls; der Worker dieses Artikels batcht außerdem Tools, deren Dependencies bereit sind. |
| Geeignet für | Ergebnisoffene Exploration oder Aufgaben mit unvorhersehbaren Ergebnissen. | Long-Horizon-Aufgaben, die ein stabiles Ziel erfordern (z. B. das Schreiben einer wissenschaftlichen Arbeit). | Strukturierte, wiederholbare Workflows (z. B. das Abrufen des Wetters in fünf Städten). |
Die Tabelle dient als Routing-Hilfe, nicht als Benchmark. Verwende ReAct, wenn ein Tool Result die nächste Action ändern kann. Verwende Plan-and-Execute, wenn sich die Aufgabe in Schritte aufteilen lässt, aber jeder Schritt weiterhin Feedback benötigt. Füge einen Replanner nur hinzu, wenn ein Ergebnis spätere Schritte ändern muss; der folgende Lehr-Graph hat keinen. Verwende ReWOO, wenn jede Tool Dependency vor der Ausführung bekannt ist. Sein Dependency Graph ermöglicht es dem Lehr-Worker, bereite Calls parallel auszuführen und einen Graphen zu erkennen, der nicht fortschreiten kann. Mit dem execute_tool-Helper des Begleiters werden abgefangene Tool-Fehler zu Strings, die an den Solver weitergegeben werden. Eine Exception, die aus dem Helper entweicht, bricht den Worker ab. Keiner der beiden Pfade bietet Retries oder Re-Planning. Miss alle drei mit deinem Model, deiner Tool-Latency, deinem Task-Set und deiner Retry-Policy, bevor du auf die Anzahl der Calls optimierst.
Was sich mit aktuellen Models und Harnesses ändert
Diese Patterns beschreiben Entscheidungen außerhalb des Models. Ein Model, das zwischen Tool Calls Reasoning betreibt, benötigt weiterhin ein Programm, das diese Calls ausführt, den Loop beendet und Effekte autorisiert. Bei Claude Sonnet 5 ist adaptives Thinking standardmäßig aktiviert, und sein Text wird standardmäßig weggelassen. Ein leerer Thinking-Block bedeutet nicht, dass das Model das Reasoning übersprungen hat. Bewahre beim Zurückgeben von Tool Results die vollständigen signierten Thinking-Blöcke auf; wenn du die Conversation aus sichtbarem Text neu aufbaust, geht diese Kontinuität verloren. Vergleiche den Reasoning-Aufwand und die abgerechneten Output-Tokens ebenso wie die Anzahl der Model Calls.
Für eine neue LangChain-Implementierung beginnst du mit create_agent. Dieses eigenständige Wiring-Beispiel verwendet den aktuellen Sonnet-Identifier und ein lokales Fixture-Tool. Setze ANTHROPIC_API_KEY und installiere langchain sowie langchain-anthropic, bevor du es aufrufst; dieser Aufruf erzeugt kostenpflichtige Model Requests.
from langchain.agents import create_agent
from langchain_anthropic import ChatAnthropic
def lookup_fixture_company(ticker: str) -> str:
"""Look up a company name in a tiny local fixture, not live market data."""
return {"NVDA": "NVIDIA", "AMD": "AMD"}.get(
ticker.strip().upper(), "No company in the fixture"
)
agent = create_agent(
model=ChatAnthropic(model="claude-sonnet-5", max_tokens=4096),
tools=[lookup_fixture_company],
system_prompt="Use the fixture for company names. Do not invent market data.",
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Which company is NVDA in the fixture?"}]
})
print(result["messages"][-1].content)
Dies ist ein beobachtungsbasierter Tool Loop. Für diese eine Abfrage ist kein separater Planner erforderlich. Sampling-Overrides lasse ich im Beispiel weg: Ein Model-Upgrade erfordert außerdem die Prüfung der akzeptierten Parameter, Response-Blöcke und des Verhaltens bei Structured Output. Das Beispiel wurde offline auf Imports und Konstruktion geprüft, aber nicht mit Live-Inference evaluiert.
Wenn der Job isolierte Subagent-Kontexte, Dateien und automatisches Context Management benötigt, bündelt Deep Agents diese Fähigkeiten rund um denselben Tool Loop. Das ist eine Harness-Entscheidung, kein vierter Reasoning-Algorithmus. Vergleiche einen einzelnen Agent mit Delegation bei Tasks, die sich tatsächlich in unabhängige Arbeiten aufteilen lassen; berücksichtige dabei Koordinationskosten und verlorenen Kontext im Ergebnis.
Ein durchgearbeitetes Beispiel: der Market Analyst Agent
Der Market Analyst Agent macht den Unterschied konkret. Eine Codebasis verwendet für Market Research alle drei Patterns, und ein Router wählt zwischen einem Deep-Research-Pfad und einem Flash-Briefing-Pfad. Die folgenden Ausschnitte sind gekürzte Lehrvarianten von Commit b4e769a: Der Worker fügt parallel ausführbare Batches hinzu, sobald deren Dependencies bereitstehen, und löst einen Fehler aus, wenn der Graph nicht fortschreiten kann. Sein execute_tool ist der zugehörige Helper, der Tool Exceptions abfängt und Error-Strings für den Solver zurückgibt. Nur Exceptions, die aus diesem Helper entkommen, brechen einen Future ab. Diese Lehradaption fügt weder Retries noch Recovery hinzu.
Für die Orchestration wird LangGraph verwendet. Ein Node ist eine Python-Funktion, die Felder zurückgibt, mit denen der gemeinsame State aktualisiert wird. Eine Edge deklariert den nächsten Node und kann eine Routing-Funktion aufrufen. LangGraph führt Updates zusammen und erstellt an Super-Step-Grenzen Checkpoints: für einen Node oder einen Batch paralleler Nodes. Ausstehende Writes bewahren erfolgreiche Ergebnisse von Geschwistern, wenn ein anderer Node fehlschlägt. Die drei Patterns teilen sich ein State-Objekt; daher benötigt das Routing nicht drei separate Schemas:
Das Diagramm isoliert Routing und die Erstellung des Entwurfs. Es lässt den gemeinsamen Evaluator und die menschliche Freigabe vor dem Publishing aus, die später gezeigt werden, damit die beiden Reasoning Loops übersichtlich bleiben.
State-Definition
Die folgenden Python-Blöcke sind Integrationsauszüge, keine eigenständigen Skripte: Sie verwenden gemeinsame State-Typen, Node-Helfer und Framework-Imports aus dem Begleitmaterial. Der isolierte Example Runner des Repositorys überspringt sie; Offline-Contract-Checks decken State, vorgeschlagene Calls, Message-IDs und das Resume-Verhalten ab.
Das State-Schema enthält die Felder, die beide Modi benötigen:
from typing import Literal
class PlanStep(BaseModel):
"""A single step in the research plan."""
step_number: int
description: str
tool_hint: str | None = None
completed: bool = False
result: str | None = None
class UserProfile(BaseModel):
"""Structured user context loaded from long-term memory."""
risk_tolerance: str | None = None
investment_horizon: str | None = None
class AgentState(BaseModel):
"""Main state for the Market Analyst Agent graph."""
# Identity and profile context for memory-backed personalization
user_id: str
user_profile: UserProfile = Field(default_factory=UserProfile)
# Message history with LangGraph's add_messages reducer
messages: Annotated[list, add_messages] = Field(default_factory=list)
# Execution mode (set by router)
execution_mode: ExecutionMode | None = None
# Plan-and-Execute state
plan: list[PlanStep] = Field(default_factory=list)
current_step_index: int = 0
# ReWOO state
rewoo_plan: list[ReWOOPlanStep] = Field(default_factory=list)
# Research results
research_data: ResearchData | None = None
# Final report. Both paths write this field, then the graph pauses before
# publishing (see interrupt_before below), so a human signs off on a draft
# a fresh-context evaluator has already voted on.
draft_report: DraftReport | None = None
report_approved: bool = False
evaluator_verdict: Literal["pass", "fail", "needs_human"] | None = None
evaluator_reasons: list[str] = Field(default_factory=list)
Pattern 1: Plan-and-Execute-Implementierung
Plan-and-Execute eignet sich für mehrstufige Synthese. Ein Planner schreibt die übergeordneten Schritte, anschließend führt ein ReAct Loop jeden Schritt aus und reagiert auf Tool Results.
Die folgenden historischen Auszüge aus dem Begleitmaterial behalten claude-sonnet-4-5-20250929 und die ältere create_react_agent API bei, damit sie mit dem verlinkten Commit vergleichbar bleiben. Der aktuelle Ausgangspunkt ist das oben gezeigte Beispiel create_agent. Für die Migration des vollständigen Graphen müssen dessen Planner-Schemas, das Handling der inneren Messages, Routing und Resume-Verhalten gemeinsam getestet werden; nur den Model-String zu ändern, ist keine solche Migration.
Die Implementierung macht vier Grenzen sichtbar:
- Eine einmalige vorgelagerte Planungsphase. Ein einzelner LLM-Call erzeugt den vollständigen Plan als Liste von Schrittbeschreibungen.
- Schema-Guided Output zur Validierung der Response-Struktur. Für die Ausführung ist eine separate Planprüfung erforderlich.
- Noch keine Tool Execution. Der Planner entscheidet nur, was zu tun ist, nicht wie.
- Für Menschen lesbare Schritte. Jeder Schritt ist Text, den ein Executor interpretiert.
# System prompt guides the LLM to think like a research analyst
# creating a strategic plan, not immediate tool calls
PLANNER_SYSTEM_PROMPT = """You are a senior investment research analyst.
Break down stock analysis requests into 4-6 research steps covering:
1. Current price and basic metrics
2. Recent news and announcements
3. Competitor analysis (if relevant)
4. Financial health assessment
5. Risk factors
6. Investment thesis synthesis
Output as JSON with step_number, description, and tool_hint."""
# Schema-Guided Reasoning: Enforce structure with Pydantic
class PlanOutput(BaseModel):
"""Structured output for the planner."""
steps: list[PlanStep] = Field(description="Research steps to execute")
ticker: str = Field(description="The stock ticker being analyzed")
def planner_node(state: AgentState) -> dict:
"""Generate a research plan from the user's request.
This is Phase 1 of Plan-and-Execute: creating the high-level strategy.
"""
# Use a powerful model for strategic planning
llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)
# Ask for a typed plan and validate it before execution.
# The API can still fail, so production code also handles that exception.
structured_llm = llm.with_structured_output(PlanOutput)
# Pull the request out of the message history
human = [m for m in state.messages if isinstance(m, HumanMessage)]
last_user_message = human[-1].content if human else "Analyze the market"
# Context from long-term memory personalizes the plan
profile_context = f"""
User Profile:
- Risk Tolerance: {state.user_profile.risk_tolerance}
- Investment Horizon: {state.user_profile.investment_horizon}
"""
# Single LLM call creates the complete plan
result: PlanOutput = structured_llm.invoke([
SystemMessage(content=PLANNER_SYSTEM_PROMPT + profile_context),
HumanMessage(content=f"Create a research plan for: {last_user_message}"),
])
# State update: Store the plan and initialize tracking
return {
"plan": result.steps, # The sequential steps to execute
"current_step_index": 0, # Start at step 0
"research_data": ResearchData(ticker=result.ticker), # Initialize data container
}
Die Zeile llm.with_structured_output(PlanOutput) steht für Schema-Guided Reasoning (SGR), das ich in einem früheren Beitrag behandelt habe. Das Schema weist fehlerhafte Felder zurück. Vor der Ausführung sollte außerdem eine nichtleere, begrenzte Liste von Schritten mit eindeutigen Schrittnummern und brauchbaren Beschreibungen verlangt werden; diese verkürzte Schema-Definition erzwingt diese Bedingungen nicht. Das Begleitmaterial kann weiterhin einen leeren Plan akzeptieren und erst fehlschlagen, wenn sein Executor den ersten Schritt indiziert.
Pattern 2: ReAct Execution
Sobald der Plan existiert, führt der Executor jeden Schritt als eigenen ReAct Loop aus. Das ist Phase 2: Jeder Schritt ist klein genug, dass ein Thought-Action-Observation-Zyklus fokussiert bleibt, und der Agent kann auf beliebige Tool Results reagieren.
So ist der ReAct-Teil aufgebaut:
- Iterative Ausführung: ein Schritt nach dem anderen mit Feedback aus den Observations.
- Der Model-/Tool-Result-Loop läuft innerhalb von
create_react_agent; die Factory garantiert keinen zugänglichen Reasoning-Transcript. - Die Results vorheriger Schritte werden als Context für das aktuelle Reasoning eingespeist.
- Der Agent wählt Tools anhand der Schrittbeschreibung aus.
- Er kann seine Vorgehensweise während des Schritts auf Basis des Tool Results ändern.
# The five market-data tools the ReAct agent chooses from here. The repo's
# TOOLS list carries four more — a skill loader, two CLI wrappers, and a
# restricted in-process Python evaluator — covering three of the five tool
# modalities Part 3 compares. MCP is the fourth, and it lives in a sidecar
# rather than in this list.
TOOLS = [
get_stock_snapshot,
get_price_history,
search_news,
search_competitors,
get_financials,
]
def executor_node(state: AgentState) -> dict:
"""Execute the current step using a ReAct agent.
This is Phase 2 of Plan-and-Execute: adaptive execution of each planned step.
Each step runs as a mini ReAct loop until completion.
"""
# Get the current step from the plan
current_step = state.plan[state.current_step_index]
# Build context from what we've learned so far
# This matters: each step builds on previous observations
previous_context = ""
for step in state.plan[:state.current_step_index]:
if step.result:
previous_context += f"\nStep {step.step_number}: {step.result}\n"
# Create a ReAct agent for this step
# This companion example pins LangGraph's deprecated create_react_agent API.
# Current LangChain guidance recommends create_agent instead:
# https://reference.langchain.com/python/langgraph.prebuilt/chat_agent_executor/create_react_agent
# The observable loop is a proposed call, its result, and the next decision.
# A visible reasoning block depends on the model and API configuration.
react_agent = create_react_agent(
model=ChatAnthropic(model="claude-sonnet-4-5-20250929"),
tools=TOOLS,
)
# Invoke the ReAct loop for this single step
# The agent will loop internally until it completes the step
result = react_agent.invoke({
"messages": [
SystemMessage(content=EXECUTOR_SYSTEM_PROMPT),
HumanMessage(content=f"""Execute Step {current_step.step_number}:
{current_step.description}
Ticker: {state.research_data.ticker}
Previous findings: {previous_context}"""),
]
})
# Extract the final answer from the ReAct agent's message history
# The last message contains the synthesis after all tool calls
updated_plan = list(state.plan)
updated_plan[state.current_step_index] = PlanStep(
step_number=current_step.step_number,
description=current_step.description,
completed=True,
result=result["messages"][-1].content, # Final synthesized answer
)
# State update: Mark step complete and advance to next
return {
"plan": updated_plan,
"current_step_index": state.current_step_index + 1,
}
Pattern 3: ReWOO für schnelle Snapshots
Für ein schnelles Briefing entfernt ReWOO Model Calls aus der Ausführungsphase. Unabhängige Tools laufen parallel; abhängige Tools warten auf ihre Voraussetzungen. Der Planner gibt den Tool-Graph vorab aus, und der Worker führt ihn aus, ohne das Model zu fragen, was als Nächstes zu tun ist.
Die Struktur sieht so aus:
- Drei Phasen (Planner → Worker → Solver). Der Worker fragt das Model nicht, was als Nächstes zu tun ist, und erstellt keinen neuen Plan.
- Tool Calls referenzieren die Platzhalter
#E1und#E2für Ergebnisse, die noch nicht existieren. - Während der Ausführung wird kein LLM verwendet. Der Worker führt lediglich Tools aus.
- Unabhängige Tools werden parallel ausgeführt.
- Am Ende erfolgt ein einziger Synthesis-Call über alle Daten gleichzeitig.
Phase 1: ReWOO Planner (schreibt vorab einen typisierten Plan mit vorgeschlagenen Calls)
class ReWOOPlanStep(BaseModel):
"""A step in the ReWOO plan with variable placeholders.
Key difference from Plan-and-Execute's PlanStep:
- Contains actual tool_name and tool_args (not just description)
- Uses variable references (#E1) for dependencies
"""
step_id: str # e.g., "#E1" - becomes a variable
description: str
tool_name: str # Requested tool name; validate it against a registry in production
tool_args: dict # Proposed arguments; may contain refs like {"price": "#E1"}
depends_on: list[str] = [] # Declared ordering; cross-check it against #E placeholders
result: str | None = None
class ReWOOPlanOutput(BaseModel):
"""Structured output for ReWOO planner."""
steps: list[ReWOOPlanStep] = Field(description="Planned tool calls with variables")
def rewoo_planner_node(state: AgentState) -> dict:
"""Generate a complete plan of tool calls upfront.
This is the key difference from Plan-and-Execute: instead of creating
human-readable step descriptions, it creates typed proposed tool calls.
Production code validates them before execution; this teaching worker does not.
"""
llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)
# Schema-Guided Reasoning validates the planner response shape.
# It does not validate a tool name, its arguments, or its dependencies.
structured_llm = llm.with_structured_output(ReWOOPlanOutput)
ticker = state.research_data.ticker if state.research_data else "UNKNOWN"
human = [m for m in state.messages if isinstance(m, HumanMessage)]
query = human[-1].content if human else f"Analyze {ticker}"
# Single LLM call to plan ALL tool executions
result: ReWOOPlanOutput = structured_llm.invoke([
SystemMessage(content=REWOO_PLANNER_PROMPT),
HumanMessage(content=f"""Create a ReWOO plan for: {query}
Ticker: {ticker}
Output tool calls with:
- step_id: Variable name (#E1, #E2, etc.)
- description: What this accomplishes
- tool_name: Exact tool from the list
- tool_args: Dictionary of arguments
- depends_on: List of step_ids this depends on"""),
])
# Store the typed proposed-call plan.
# This teaching worker sends ready steps directly to execute_tool.
return {"rewoo_plan": result.steps}
Phase 2: ReWOO Worker (führt Tools ohne LLM Reasoning aus)
def rewoo_worker_node(state: AgentState) -> dict:
"""Execute dependency-ready tools in parallel batches (no LLM calls).
Independent tools share a batch. Dependent tools wait until their
prerequisites complete. The worker follows the dependency graph and
does not add LLM calls.
"""
results = {} # Results keyed by step_id (e.g., "#E1": "$150.23")
updated_steps = [] # Plan steps with their result field filled in
pending = {step.step_id: step for step in state.rewoo_plan}
# Keep scheduling dependency-ready batches until the graph is complete.
# This handles chains even when the planner does not list them topologically.
with ThreadPoolExecutor(max_workers=5) as executor:
while pending:
ready = [
step for step in pending.values()
if all(dep in results for dep in step.depends_on)
]
if not ready:
unresolved = ", ".join(pending)
raise ValueError(f"Unresolvable ReWOO dependencies: {unresolved}")
futures = {
executor.submit(execute_tool, step, results): step
for step in ready
}
for future in as_completed(futures):
step = futures[future]
results[step.step_id] = future.result()
updated_steps.append(step.model_copy(update={"result": results[step.step_id]}))
del pending[step.step_id]
# State update: restore the planner's order (sorting on step_id would put
# "#E10" before "#E2") and hand the filled-in plan to the Solver
plan_order = {s.step_id: i for i, s in enumerate(state.rewoo_plan)}
return {"rewoo_plan": sorted(updated_steps, key=lambda s: plan_order[s.step_id])}
Phase 3: ReWOO Solver (führt alle Ergebnisse in einem einzigen LLM Call zusammen)
def rewoo_solver_node(state: AgentState) -> dict:
"""Synthesize all tool results into a flash briefing.
This is the second efficiency gain: Instead of interleaving
LLM calls with tool execution (like ReAct), we make ONE
final synthesis call with all gathered data.
"""
# Build context from ALL tool results at once
tool_results = []
for step in state.rewoo_plan:
if step.result:
tool_results.append(f"### {step.description}\n{step.result}")
context = "\n\n".join(tool_results)
# Single LLM call to synthesize everything
llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)
structured_llm = llm.with_structured_output(FlashBriefingOutput)
result = structured_llm.invoke([
SystemMessage(content=REWOO_SOLVER_PROMPT),
HumanMessage(content=f"Create a flash briefing from this data:\n\n{context}"),
])
# FlashBriefingOutput and DraftReport carry the same fields; the state
# schema expects DraftReport, so convert before returning.
return {"draft_report": DraftReport(**result.model_dump())}
Der Planner schreibt vorgeschlagene Calls, der Worker ersetzt deren Platzhalter, und der Solver erhält die Ergebnisse. Das Schema prüft lediglich die Form der Antwort. Leere oder übergroße Pläne, doppelte IDs, fehlende Abhängigkeiten und Zyklen müssen vor dem Dispatch abgewiesen werden. Doppelte IDs werden im oben genannten pending-Dictionary stillschweigend zusammengeführt. Prüfe Tool-Namen und Argumente anhand einer Registry, untersuche Platzhalter rekursiv innerhalb verschachtelter Listen und Objekte, gleiche sie mit depends_on ab und weise ungelöste Referenzen zurück, bevor der betroffene Call ausgeführt wird. Dieser Teaching Worker überspringt diese Prüfungen und sendet fertige Schritte an das execute_tool des Companions, einschließlich dessen Verhalten, Fehler als Evidenz zu behandeln. Der Solver muss einen Fehler-String als fehlende Evidenz und nicht als erfolgreichen Lookup behandeln. Wenn sein Graph nicht weiter fortschreiten kann, beendet er die Ausführung vor dem Solver; es gibt weder einen Retry- noch einen Replanning-Pfad. Das erneute Ausführen desselben Zustands wiederholt lediglich denselben Plan. Replanning benötigt daher ein Fehlerergebnis, eine bedingte Kante und eine Begrenzung der Versuchsanzahl.
Wo jedes Pattern das Model aufruft
| Pattern | LLM Calls während der Ausführung | State-Updates | Wichtiges Code-Pattern |
|---|---|---|---|
| Plan-and-Execute | 1 für die Planung + ein ReAct Loop pro Schritt (jeweils mehrere Calls) + 1 für den Bericht | Sequentieller Abschluss der Schritte | planner_node() → Loop: executor_node() → reporter_node() |
| ReAct (innerhalb jedes Schritts) | Mehrere pro Schritt (Thought-Action-Zyklen) | Nur das interne Transcript; der äußere Graph erfasst die Ergebnisse abgeschlossener Schritte | Der festgelegte Companion verwendet das veraltete create_react_agent() |
| ReWOO | 1 für die Planung + 0 während der Ausführung + 1 für die Synthese | Abhängigkeitsbewusste Tool-Batches | rewoo_planner_node() → rewoo_worker_node() → rewoo_solver_node() |
Der entscheidende Unterschied ist die Ausgabe des Planners. Sie bestimmt, wie viel Entscheidungsspielraum der Executor behält:
-
Plan-and-Execute erstellt für Menschen lesbare Schrittbeschreibungen:
# Planner output (list of PlanStep objects) plan = [ PlanStep( step_number=1, description="Get current price and key financial metrics", tool_hint="get_stock_snapshot" ), PlanStep( step_number=2, description="Search for recent news and earnings", tool_hint="search_news" ), # ... more steps ]Der Executor liest jede Beschreibung und entscheidet, welche Tools aufgerufen werden. Das ist flexibel, aber jeder Schritt ist ein eigener ReAct Loop. Daher kostet ein Schritt mehrere Model Calls und nicht nur einen.
-
ReAct verfügt nicht über einen vorgelagerten Plan. Stattdessen verwendet es iteratives Reasoning:
# A standalone full-history ReAct loop carries messages from earlier turns. messages = [ HumanMessage(content="Execute Step 1: Get current price"), AIMessage(content="", tool_calls=[{ "id": "1", "name": "get_stock_snapshot", "args": {"ticker": "NVDA"}, "type": "tool_call", }]), ToolMessage(tool_call_id="1", content="$132.45"), AIMessage(content="Now I need metrics..."), # ... agent continues until step complete ]In einem eigenständigen ReAct Loop mit vollständiger Historie enthält jeder Model-Aufruf eine Historie, die während der gesamten Aufgabe wächst. Cache Hits können wiederholte Berechnungen und Input-Kosten reduzieren; ein Production Loop kann die Historie zusätzlich zusammenfassen oder kürzen. Das Plan-and-Execute-Beispiel startet jede ReAct-Invocation mit dem aktuellen Schritt und einer Zusammenfassung der bisherigen Erkenntnisse. Das abgeschlossene Ergebnis wird in
plangespeichert, nicht im Transcript der inneren Tool Calls. -
ReWOO erzeugt explizite, typisierte vorgeschlagene Tool Calls:
# Planner output (list of ReWOOPlanStep objects) rewoo_plan = [ ReWOOPlanStep( step_id="#E1", description="Read the current price and valuation snapshot", tool_name="get_stock_snapshot", tool_args={"ticker": "NVDA"} ), ReWOOPlanStep( step_id="#E2", description="Find recent NVDA earnings news", tool_name="search_news", tool_args={"query": "NVDA earnings", "max_results": 5} ), # ... all tool calls planned upfront ]Der Worker läuft ohne Kontext und ohne Beteiligung eines LLM. Alle Model-Aufrufe finden im Planner und Solver statt, wodurch die Anzahl der Model-Aufrufe vorhersehbar wird.
Memory- und State-Fluss:
- Plan-and-Execute: Der State fließt über
plan→current_step_index→research_data. - ReAct innerhalb dieses Plan-and-Execute-Graphen: Der innere Agent erzeugt das Transcript eines einzelnen Schritts. Der äußere Graph behält
plan,current_step_indexund die Ergebnisse der abgeschlossenen Schritte. - ReWOO: Der State fließt über
rewoo_plan, wobei der Worker die Felderresultausfüllt.
Beide Routen in einen Graphen integrieren
Der Graph stellt über einen gemeinsamen AgentState zwei benutzerseitige Routen bereit: Deep Research verwendet Plan-and-Execute mit einem ReAct Loop innerhalb jedes Schritts, während Flash Briefing ReWOO verwendet. ReAct ist hier ein Ausführungsprimitiv und keine dritte Route.
Diese Implementierung hat keinen Replanner: Sie führt den initialen Plan vollständig aus. Für Replanning wären eine Kante von executor zurück zu planner sowie eine Regel erforderlich, die festlegt, wann ein überraschendes Ergebnis einen weiteren Model-Aufruf rechtfertigt.
LangGraph hält die Verdrahtung deklarativ:
def create_graph(checkpointer=None):
builder = StateGraph(AgentState)
# Add nodes
builder.add_node("router", router_node)
builder.add_node("planner", planner_node)
builder.add_node("executor", executor_node)
builder.add_node("reporter", reporter_node)
builder.add_node("rewoo_planner", rewoo_planner_node)
builder.add_node("rewoo_worker", rewoo_worker_node)
builder.add_node("rewoo_solver", rewoo_solver_node)
builder.add_node("evaluator", evaluator_node)
builder.add_node("publish", publish_node)
# Define edges
builder.add_edge(START, "router")
builder.add_conditional_edges("router", route_after_router, {
"planner": "planner",
"rewoo_planner": "rewoo_planner",
})
# Deep Research path
builder.add_edge("planner", "executor")
builder.add_conditional_edges("executor", route_after_executor, {
"executor": "executor", # Loop back for more steps
"reporter": "reporter", # Done with plan
})
builder.add_edge("reporter", "evaluator")
# Flash Briefing path (ReWOO)
builder.add_edge("rewoo_planner", "rewoo_worker")
builder.add_edge("rewoo_worker", "rewoo_solver")
builder.add_edge("rewoo_solver", "evaluator")
# Both paths run the same evaluator before the human approval step.
builder.add_edge("evaluator", "publish")
builder.add_edge("publish", END)
return builder.compile(
checkpointer=checkpointer,
# Human-in-the-loop pause: the reporter (or ReWOO solver) writes a
# draft. A separate model session reads that draft and records its
# assessment. The graph then stops before publishing, whichever
# assessment it produced, so a human can review both the draft and
# assessment. Part 6 explains how the program decides a run is complete.
interrupt_before=["publish"],
)
Automatische Pattern-Auswahl mit einem Router
Der Router ordnet die Form der Anfrage einer Route zu. Schema-Guided Reasoning schränkt den Output des Classifiers ein:
class ExecutionMode(str, Enum):
"""Execution mode for the agent."""
DEEP_RESEARCH = "deep_research" # Plan-and-Execute + ReAct (thorough)
FLASH_BRIEFING = "flash_briefing" # ReWOO (fast, token-efficient)
class RouterOutput(BaseModel):
"""Structured output for the router."""
mode: ExecutionMode # DEEP_RESEARCH or FLASH_BRIEFING
ticker: str
reasoning: str
ROUTER_SYSTEM_PROMPT = """Classify the user's request:
1. **deep_research**: Complex analysis requiring synthesis
- Examples: "Analyze strategic risks", "investment thesis"
2. **flash_briefing**: Quick snapshots, simple data retrieval
- Examples: "quick snapshot", "current price"
Default to deep_research if unclear."""
llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)
structured_llm = llm.with_structured_output(RouterOutput)
Mit diesem Router wird „aktueller Preis“ an ReWOO und „Investmentthese“ an Plan-and-Execute weitergeleitet. Bei mehrdeutigen Anfragen ist Deep Research der Default. Bevor der Router vor Benutzern eingesetzt wird, sollten beide Routen anhand eines festen Workflows mit denselben Aufgaben, Tools, Evidenzdaten und demselben Budget verglichen werden. Wiederhole stochastische Trials und zähle jeden gestarteten Versuch. Erfasse Task Success, Incorrect Acceptance, gecachte und nicht gecachte Tokens, Wall Time sowie die Recovery nach Tool-Fehlern oder irreführenden Beobachtungen. Diese Patterns sind Entscheidungen zum Control Flow und keine Rangfolge für die Einführung; auch Anthropics Engineering-Bericht empfiehlt, mit einfachen, komponierbaren Workflows zu beginnen.
Die vollständige Companion-Implementierung einschließlich Router und gemeinsamem State befindet sich im gepinnten Market Analyst Agent Commit.
Die nächste Ebene ist Memory
Teil 2, AI Agent Memory Architecture, trennt wiederaufnehmbare Checkpoints von sitzungsübergreifendem Wissen und Projektdokumenten. Ohne diese State-Schicht funktionieren der Router und der Executor oben nur, solange ein einzelner Prozess und ein einzelnes Context Window aktiv bleiben.
Referenzen
- ReAct: Synergizing Reasoning and Acting in Language Models (Yao et al., 2022)
- ReWOO: Decoupling Reasoning from Observations for Efficient Augmented Language Models (Xu et al., 2023)
- Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models (Wang et al., 2023)
- Market Analyst Agent Repository