Pętle rozumowania agentów AI: ReAct, ReWOO, Plan-and-Execute
Tłumaczenie automatyczne Ten artykuł został automatycznie przetłumaczony z angielskiego oryginału.
Aktualizacja artykułu
Pierwotnie opublikowano 31 stycznia 2026 r. Artykuł zaktualizowano i ponownie zrecenzowano 6 września 2026 r. Aktualizacja koncentruje się na nowszych możliwościach modeli i reasoning APIs oraz zawiera zmienione przykłady i linki do źródeł.
Pętla rozumowania agenta to przepływ sterowania, który decyduje, kiedy model planuje, wywołuje narzędzie, odczytuje wynik i kończy działanie. Dla inżyniera budującego agenta ten wybór jest również budżetem: określa, jak często uruchamiany jest model, ile historii niesie każde wywołanie oraz czy zaskakujący wynik może zmienić kolejne działanie.
W tym artykule porównuję ReAct, ReWOO oraz Plan-and-Execute na przykładzie agenta analityka rynku zbudowanego w LangGraph. Poznasz regułę routingu i schematy implementacji, które można dostosować — zamiast trzech nazw do dodania na diagramie.
Pętla jest najbardziej wewnętrzną warstwą tej serii. Otaczają ją pamięć, narzędzia, bezpieczeństwo, runtime oraz kontrole wykonywane przed uznaniem zadania za zakończone; nie zastępują one pętli.
Krótkie porównanie frameworków znajdziesz w Najlepsze frameworki agentów AI w 2026 roku.
Pętla rozumowania decyduje, co zrobić dalej. Nie przechowuje stanu, nie wykonuje narzędzi ani nie autoryzuje efektów ubocznych.
Harness to program sterujący między modelem a maszyną. Składa prompty ze stanu przechowywanego w pamięci (Część 2), definiuje działania, które model może nazwać (Część 3), autoryzuje wywołania (Część 4) oraz sprawdza dowody przed uznaniem zadania za zakończone (Część 6). Są to odrębne problemy inżynieryjne, ale pojedyncza tura przechodzi przez wszystkie cztery.
Runtime (Część 5) dostarcza log sesji, sandbox, magazyn checkpointów oraz trace’y, które przetrwają dłużej niż pojedynczy proces workera.
Każdy artykuł stanowi samodzielną całość. Razem prowadzą od pętli na zewnątrz.
Zacznij od granicy awarii
Dobry prompt nie rozstrzyga kwestii przepływu sterowania. Wzorce różnią się tym, ile pracy jest ustalone przed pierwszym wywołaniem narzędzia. To określa liczbę wywołań modelu, moment ujawnienia błędnego planu oraz to, czy nieoczekiwany wynik narzędzia może przekierować wykonanie.
Trzy wzorce rozumowania agentów AI
ReAct: podejmowanie decyzji po każdej obserwacji
ReAct (Yao et al., 2022), skrót od Reason + Act, utrzymuje kolejną decyzję blisko najnowszej obserwacji:
Oryginalny artykuł wykorzystuje jawny tekst typu Thought, Action i Observation. Współczesne natywne API narzędzi udostępniają proponowane wywołania i wyniki narzędzi; widoczna myśl nie jest wymagana. Myślenie przeplatane jest odrębną funkcją modelu/API. W historycznym schemacie promptowania:
- Thought: agent generuje „myśl”, aby rozłożyć cel na części i zaplanować kolejny krok.
- Action: na podstawie tej myśli wywołuje narzędzie.
- Observation: agent odczytuje wynik, który aktualizuje jego rozumienie problemu przed sformułowaniem kolejnej myśli.
Dzięki temu ReAct ma użyteczne właściwości:
- W ręcznym przykładzie PaLM-540B dla HotpotQA z artykułu obserwacje z Wikipedii prowadziły do mniejszej liczby halucynowanych faktów niż promptowanie chain-of-thought.
- Agent może na bieżąco zmieniać strategię na podstawie tego, co właśnie zobaczył.
- Historia wywołań narzędzi i obserwacji daje konkretny ślad wykonania.
Ta sama pętla ma jednak koszty:
- W naiwnej implementacji obejmującej całą historię, bez cachowania, każda tura ponownie przetwarza narastającą historię. Prompt caching zmienia koszt wejścia i czas przetwarzania, ale nie zmienia zajętości okna kontekstowego ani nieaktualnych obserwacji. Mierz osobno tokeny z cache i bez cache; podsumowywanie oraz obcinanie odrzucają kontekst.
- Jest nieefektywna, gdy wywołania narzędzi można było zaplanować z góry — właśnie tę niszę wypełnia ReWOO.
- Bez warunku zatrzymania lub limitu kroków pętla może działać w nieskończoność.
Używaj jej do zadań eksploracyjnych, debugowania oraz pracy, w której nie da się przewidzieć kolejnej akcji.
ReWOO: najpierw skompiluj graf narzędzi
ReWOO (Reasoning WithOut Observation) oddziela planowanie od wykonania. Planner zapisuje kompletną sekwencję narzędzi w jednym przebiegu, używając placeholderów dla wartości, które pojawią się dopiero po wykonaniu.
- Plan: jedno wywołanie LLM zapisuje kompletny plan wywołań narzędzi, używając placeholderów zmiennych (
#E1,#E2) dla wyników, które jeszcze nie istnieją. - Worker: executor niebędący LLM uruchamia zaplanowane narzędzia i uzupełnia placeholdery. Worker w artykule wykonuje plan; implementacja przedstawiona dalej dodaje zależne od zależności równoległe batch’e dla gotowych kroków.
- Solver: końcowe wywołanie LLM otrzymuje zebrane obserwacje i generuje odpowiedź.
Taki podział zapewnia:
- Mniejszą liczbę powtarzanych wywołań modelu niż w ReAct, gdy początkowy plan pozostaje poprawny.
- Mniej powtarzanej historii promptu niż w przeplatanej pętli obejmującej całą historię. Opóźnienie narzędzi nadal zależy od sposobu, w jaki worker planuje wywołania.
- Możliwość niezależnego dostrajania plannera, bez dostępu do działającego środowiska.
Tworzy on jednak twardą granicę. W teście obciążeniowym HotpotQA z artykułu każde narzędzie zwracało No evidence found; ReWOO tracił mniej dokładności niż ReAct, ponieważ nieudane obserwacje nie wprowadzały jego plannera w kolejną pętlę. Jest to względna odporność, a nie polityka odzyskiwania po błędzie wykonania. Implementacja nadal musi zdecydować, czy błąd narzędzia stanie się dowodem dla solvera, wywoła ponowienie próby, czy przerwie wykonanie. ReWOO pasuje do przewidywalnych workflowów; samodzielnie nie planuje ponownie działań wokół niepoprawnego grafu początkowego.
Używaj go do szybkich snapshotów, sprawdzania statusu oraz dashboardów, w których zachowanie narzędzi jest przewidywalne.
Plan-and-Execute: dekomponuj zadanie, a następnie reaguj lokalnie
Promptowanie Plan-and-Solve opisuje metodę promptowania, która najpierw tworzy plan, a następnie rozwiązuje podzadania. Powiązany wzorzec orkiestracji narzędzi jest powszechnie nazywany Plan-and-Execute. Przewodnik LangChain po Plan-and-Execute opisuje ten wzorzec:
- Faza planowania: agent najpierw generuje plan, który dzieli zadanie na mniejsze podzadania.
- Faza wykonania: agent następnie realizuje te podzadania jedno po drugim. Gdy wykorzystywane są narzędzia, każde podzadanie zwykle działa jako własna mała pętla ReAct, dzięki czemu executor nadal może reagować na wynik zwrócony przez narzędzie, mimo że ogólny plan pozostaje stały.
Oryginalny artykuł koncentrował się na promptowaniu zero-shot. W implementacji korzystającej z narzędzi wzorzec orkiestracji może wykonywać zaplanowane kroki sekwencyjnie i używać różnych modeli do planowania i wykonania. Taki podział modeli jest decyzją implementacyjną, a nie wynikiem ustalonym w artykule na temat Plan-and-Solve.
Rysunek przedstawia opcjonalną gałąź ponownego planowania. Graf dydaktyczny opisany w dalszej części tego artykułu jej nie wykorzystuje: feedback może zmieniać pracę w ramach jednego kroku, ale nie pozostałą część planu.
Ten wzorzec jest użyteczny, ponieważ zapewnia:
- Hierarchiczne rozumowanie odzwierciedlające sposób, w jaki ludzki ekspert dzieli projekt na mniejsze części.
- Jawna krawędź ponownego planowania umożliwia wstrzymanie pracy i ponowną ocenę sytuacji po nieoczekiwanym wyniku kroku.
- Specjalizację modeli. Planner może być kosztowny, a executor — tani.
- Po skonfigurowaniu checkpointera każdy ukończony krok może stać się punktem wznowienia.
Wady tego podejścia:
- Więcej wywołań modelu niż w ReWOO, gdy każdy krok zawiera własną pętlę ReAct.
- Więcej stanu do zarządzania.
- Przerost formy nad treścią w przypadku zapytań one-shot.
Używaj go do złożonych analiz i prac badawczych wymagających końcowej syntezy.
Wybieraj na podstawie miejsca, w którym plan może zawieść
| Cecha | ReAct (2022) | Plan-and-Execute (2023) | ReWOO (2023) |
|---|---|---|---|
| Główna filozofia | Improwizator: na podstawie ostatniego wyniku decyduje o kolejnym kroku, wykonując jedno wywołanie naraz. | Architekt: buduje pełny plan, wykonuje go, a następnie dokonuje przeglądu. | Optymalizator: kompiluje graf zależności, a następnie grupuje wywołania, które są gotowe do wykonania. |
| Przepływ pracy | Pętla iteracyjna: Thought → Action → Observation. | Dwuetapowy: faza 1 (planowanie), faza 2 (wykonanie). | Rozdzielony: Planner zapisuje graf wywołań narzędzi; Worker je uruchamia; Solver składa odpowiedź. |
| Adaptacyjność | Najwyższa: może zmienić kierunek po każdym pojedynczym wywołaniu narzędzia. | Każdy krok może reagować na swój wynik. Opcjonalny replanner może zmienić późniejsze kroki. | Najniższa: skrypt plannera jest wykonywany do końca; w trakcie działania nie następuje ponowne planowanie. |
| Wydajność | Naiwna pętla z pełną historią powtarza większą liczbę tokenów wejściowych; zarządzanie kontekstem może ograniczyć ten wzrost. | Pętla ReAct każdego kroku może zaczynać się od krótkiego kontekstu zamiast pełnej historii wykonania; po włączeniu replanner dodaje wywołania planowania. | Mniejsza liczba wywołań modelu; Worker z tego artykułu dodatkowo grupuje narzędzia, których zależności są gotowe. |
| Najlepsze zastosowanie | Eksploracja bez z góry określonego celu lub zadania, w których wyniki są nieprzewidywalne. | Zadania o długim horyzoncie, wymagające stałego celu (np. napisanie artykułu naukowego). | Ustrukturyzowane, powtarzalne przepływy pracy (np. sprawdzanie pogody w 5 miastach). |
Tabela służy do wyboru strategii, a nie do porównywania wyników benchmarków. Używaj ReAct, gdy wynik narzędzia może zmienić następne działanie. Używaj Plan-and-Execute, gdy zadanie można podzielić na kroki, ale każdy krok nadal wymaga informacji zwrotnej. Dodaj replannera tylko wtedy, gdy jeden wynik musi zmienić późniejsze kroki; poniższy graf dydaktyczny go nie zawiera. Używaj ReWOO, gdy wszystkie zależności narzędzi są znane przed wykonaniem. Jego graf zależności pozwala dydaktycznemu Workerowi uruchamiać równolegle gotowe wywołania i wykrywać graf, który nie może posunąć się naprzód. Za pomocą pomocnika execute_tool z companiona przechwycone błędy narzędzi stają się ciągami przekazywanymi do Solvera. Wyjątek, który wydostanie się poza pomocnika, przerywa działanie Workera. Żadna z tych ścieżek nie zapewnia ponawiania prób ani ponownego planowania. Przed optymalizacją pod kątem liczby wywołań zmierz wszystkie trzy strategie z użyciem swojego modelu, opóźnienia narzędzi, zestawu zadań i zasad ponawiania prób.
Co się zmienia w przypadku obecnych modeli i harnessów
Te wzorce opisują decyzje podejmowane poza modelem. Model, który rozumuje między wywołaniami narzędzi, nadal potrzebuje programu uruchamiającego te wywołania, zatrzymującego pętlę i autoryzującego efekty uboczne. W przypadku Claude Sonnet 5 adaptive thinking jest domyślnie włączone, a jego tekst jest domyślnie pomijany. Pusty blok thinking nie oznacza, że model pominął rozumowanie. Przy zwracaniu wyników narzędzi zachowuj kompletne, podpisane bloki thinking; odtworzenie konwersacji na podstawie widocznego tekstu powoduje utratę tej ciągłości. Porównuj effort związany z rozumowaniem i rozliczaną liczbę tokenów wyjściowych, a także liczbę wywołań modelu.
W nowej implementacji LangChain zacznij od create_agent. Ten samowystarczalny przykład konfiguracji korzysta z bieżącego identyfikatora Sonnet i lokalnego narzędzia fixture. Przed jego uruchomieniem ustaw ANTHROPIC_API_KEY oraz zainstaluj langchain i langchain-anthropic; wywołanie to wykonuje płatne żądania do modelu.
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)
Jest to pętla narzędziowa sterowana obserwacjami. W przypadku tego pojedynczego wyszukiwania nie jest potrzebny osobny planner. W przykładzie pomijam nadpisywanie parametrów próbkowania: aktualizacja modelu wymaga również sprawdzenia akceptowanych parametrów, bloków odpowiedzi i zachowania ustrukturyzowanych danych wyjściowych. Przykład sprawdzono offline pod kątem importów i konstrukcji, ale nie ewaluowano go przy użyciu działającej inferencji.
Jeśli zadanie wymaga odizolowanych kontekstów subagentów, plików i automatycznego zarządzania kontekstem, Deep Agents pakuje te możliwości wokół tej samej pętli narzędziowej. To wybór harnessu, a nie czwarty algorytm rozumowania. Porównuj pojedynczego agenta z delegowaniem na zadaniach, które faktycznie dzielą się na niezależne prace; uwzględnij w wyniku koszt koordynacji i utratę kontekstu.
Przykład: agent Market Analyst
Market Analyst Agent konkretyzuje tę różnicę. Jedna baza kodu wykorzystuje wszystkie trzy wzorce do badań rynku, a router wybiera między ścieżką deep research a ścieżką flash briefing. Poniższe fragmenty to skrócone warianty dydaktyczne commita b4e769a: poniższy worker dodaje równoległe batch’e gotowe do wykonania po spełnieniu zależności i zgłasza błąd, gdy graf nie może kontynuować działania. Jego execute_tool to pomocnicza funkcja towarzysząca, która przechwytuje wyjątki narzędzi i zwraca solverowi ciągi błędów. Tylko wyjątki, które wydostaną się z tej funkcji pomocniczej, przerywają future. Ta adaptacja dydaktyczna nie dodaje retry ani mechanizmów odzyskiwania.
Do orkiestracji wykorzystuje LangGraph. Węzeł to funkcja Python zwracająca pola do aktualizacji we współdzielonym stanie. Krawędź deklaruje kolejny węzeł i może wywoływać funkcję routingu. LangGraph scala aktualizacje i tworzy checkpointy na granicach superkroków: pojedynczego węzła lub batcha równoległych węzłów. Oczekujące zapisy zachowują pomyślne wyniki węzłów równoległych, gdy inny węzeł zakończy się błędem. Wszystkie trzy wzorce współdzielą jeden obiekt stanu, więc routing nie wymaga trzech osobnych schematów:
Diagram wyodrębnia routing i tworzenie draftu. Pomija współdzielony moduł ewaluatora oraz akceptację człowieka przed publikacją, pokazane później, dzięki czemu obie pętle rozumowania pozostają czytelne.
Definicja stanu
Poniższe bloki Pythona to fragmenty integracji, a nie samodzielne skrypty: współdzielą typy stanu, helpery węzłów i importy frameworka z materiału towarzyszącego. Izolowany runner przykładów w repozytorium je pomija; testy kontraktowe offline obejmują stan, proponowane wywołania, identyfikatory wiadomości oraz zachowanie po wznowieniu.
Schemat stanu przenosi pola potrzebne w obu trybach:
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)
Wzorzec 1: implementacja Plan-and-Execute
Plan-and-Execute dobrze sprawdza się w syntezie wieloetapowej. Planner zapisuje kroki wysokiego poziomu, a następnie pętla ReAct wykonuje każdy krok i reaguje na wyniki narzędzi.
Poniższe historyczne fragmenty materiału towarzyszącego zachowują claude-sonnet-4-5-20250929 oraz starsze API create_react_agent, aby pozostać porównywalne z podlinkowanym commitem. Aktualnym punktem wyjścia jest powyższy przykład create_agent. Migracja całego grafu wymaga wspólnego przetestowania schematów plannera, obsługi wewnętrznych wiadomości, routingu i zachowania po wznowieniu; sama zmiana nazwy modelu nie jest taką migracją.
Implementacja wyraźnie oddziela cztery granice:
- Jedna faza planowania na początku. Pojedyncze wywołanie LLM generuje cały plan jako listę opisów kroków.
- Dane wyjściowe sterowane schematem, który waliduje strukturę odpowiedzi. Wykonanie wymaga osobnej kontroli planu.
- Brak wykonywania narzędzi na tym etapie. Planner decyduje tylko, co zrobić, a nie jak.
- Kroki czytelne dla człowieka. Każdy krok jest tekstem, który zinterpretuje executor.
# 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
}
Wiersz llm.with_structured_output(PlanOutput) implementuje Schema-Guided Reasoning (SGR), które omawiałem w poprzednim wpisie. Schemat odrzuca niepoprawne pola. Przed wykonaniem należy również wymagać niepustej i ograniczonej liczebnie listy kroków, unikalnych numerów kroków oraz użytecznych opisów; ten skrócony schemat nie wymusza tych warunków. Materiał towarzyszący nadal może zaakceptować pusty plan i zakończyć działanie błędem, gdy executor spróbuje pobrać pierwszy krok.
Wzorzec 2: wykonanie ReAct
Gdy plan jest gotowy, executor wykonuje każdy krok jako osobną pętlę ReAct. To faza 2: każdy krok jest wystarczająco mały, aby cykl Thought–Action–Observation pozostał skoncentrowany, a agent mógł reagować na dowolny wynik zwrócony przez narzędzie.
Część ReAct działa następująco:
- Iteracyjne wykonanie. Jeden krok naraz, z informacją zwrotną w postaci obserwacji.
- Pętla model–wynik narzędzia działa wewnątrz
create_react_agent; fabryka nie gwarantuje udostępnienia transkryptu rozumowania. - Wyniki poprzednich kroków są przekazywane jako kontekst bieżącego rozumowania.
- Agent wybiera narzędzia na podstawie opisu kroku.
- Może zmienić podejście w trakcie kroku na podstawie tego, co zwróci narzędzie.
# 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,
}
Wzorzec 3: ReWOO do szybkich snapshotów
W przypadku krótkiego briefingu ReWOO usuwa wywołania modelu z fazy wykonania. Niezależne narzędzia działają równolegle, a narzędzia zależne czekają na swoje zależności. Planner emituje graf narzędzi z wyprzedzeniem, a worker wykonuje go bez pytania modelu o kolejny krok.
Schemat wygląda następująco:
- Trzy fazy (Planner → Worker → Solver). Worker nie pyta modelu, co zrobić dalej, ani nie tworzy planu ponownie.
- Wywołania narzędzi odwołują się do placeholderów
#E1,#E2reprezentujących wyniki, które jeszcze nie istnieją. - Podczas wykonywania nie jest używany LLM. Worker po prostu uruchamia narzędzia.
- Niezależne narzędzia są uruchamiane równolegle.
- Na końcu wykonywane jest jedno wywołanie syntezy obejmujące wszystkie dane naraz.
Faza 1: planner ReWOO (z góry zapisuje typowany plan proponowanych wywołań)
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}
Faza 2: worker ReWOO (wykonuje narzędzia bez rozumowania LLM)
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])}
Faza 3: solver ReWOO (syntetyzuje wszystkie wyniki w jednym wywołaniu LLM)
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())}
Planner zapisuje proponowane wywołania, worker uzupełnia ich placeholdery, a solver otrzymuje wyniki. Schemat sprawdza wyłącznie strukturę odpowiedzi. Przed wysłaniem odrzuć puste lub zbyt duże plany, zduplikowane identyfikatory, brakujące zależności i cykle. Zduplikowane identyfikatory są po cichu scalane w słowniku pending powyżej. Sprawdzaj nazwy narzędzi i argumenty względem rejestru, rekurencyjnie analizuj placeholdery wewnątrz zagnieżdżonych list i obiektów, porównuj je z depends_on i odrzucaj nierozwiązane odwołania przed wywołaniem, którego dotyczą. Ten demonstracyjny worker pomija te kontrole i wysyła gotowe kroki do execute_tool companiona, wraz z jego mechanizmem traktowania błędów jako dowodów. Solver musi traktować tekst błędu jako brak dowodu, a nie jako pomyślne wyszukiwanie. Jeśli jego graf nie może wykonać kolejnego kroku, zatrzymuje się przed solverem; nie ma ścieżki ponowienia ani ponownego planowania. Ponowienie tego samego stanu tylko powtarza ten sam plan, dlatego ponowne planowanie wymaga wyniku błędu, warunkowej krawędzi i limitu prób.
Gdzie każdy wzorzec wywołuje model
| Wzorzec | Wywołania LLM podczas wykonywania | Aktualizacje stanu | Kluczowy wzorzec kodu |
|---|---|---|---|
| Plan-and-Execute | 1 podczas planowania + pętla ReAct dla każdego kroku (po kilka wywołań) + 1 dla raportu | Sekwencyjne ukończenie kroków | planner_node() → pętla: executor_node() → reporter_node() |
| ReAct (w każdym kroku) | Wiele na krok (cykle thought-action) | Tylko wewnętrzny transcript; zewnętrzny graf rejestruje wyniki ukończonych kroków | Companion przypięty do użycia przestarzałego create_react_agent() |
| ReWOO | 1 podczas planowania + 0 podczas wykonywania + 1 podczas syntezy | Batches narzędzi zależne od zależności | rewoo_planner_node() → rewoo_worker_node() → rewoo_solver_node() |
Kluczowa różnica dotyczy danych wyjściowych plannera. Określają one zakres swobody pozostawiony executorowi:
-
Plan-and-Execute tworzy czytelne dla człowieka opisy kroków:
# 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 ]Executor odczytuje każdy opis i decyduje, które narzędzia wywołać. To elastyczne rozwiązanie, ale każdy krok jest osobną pętlą ReAct, więc koszt kroku to kilka wywołań modelu, a nie jedno.
-
ReAct nie ma planu tworzonego z wyprzedzeniem. Korzysta z iteracyjnego rozumowania:
# 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 ]W samodzielnej pętli ReAct z pełną historią każde wywołanie modelu przenosi historię, która rośnie przez całe zadanie. Trafienia w cache mogą ograniczyć ponowne obliczenia i opłaty za dane wejściowe; pętla produkcyjna może również streszczać lub skracać historię. Przykład Plan-and-Execute rozpoczyna każde wywołanie ReAct od bieżącego kroku i skrótu wcześniejszych ustaleń. Ukończony wynik przechowuje w
plan, a nie w transkrypcie wewnętrznych wywołań narzędzi. -
ReWOO tworzy jawne, typowane proponowane wywołania narzędzi:
# 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 ]Worker działa bez dostępu do kontekstu i bez udziału LLM. Wszystkie wywołania modelu znajdują się w plannerze i solverze, dzięki czemu liczba wywołań modelu jest przewidywalna.
Przepływ pamięci i stanu:
- Plan-and-Execute: stan przepływa przez
plan→current_step_index→research_data. - ReAct w ramach tego grafu Plan-and-Execute: wewnętrzny agent tworzy transkrypt jednego kroku. Zewnętrzny graf zachowuje
plan,current_step_indexoraz wyniki ukończonych kroków. - ReWOO: stan przepływa przez
rewoo_plan, a worker uzupełnia polaresult.
Połącz obie ścieżki w jeden graf
Graf udostępnia użytkownikom dwie ścieżki oparte na jednym AgentState: szczegółowy research korzysta z Plan-and-Execute z pętlą ReAct wewnątrz każdego kroku, natomiast szybki briefing korzysta z ReWOO. ReAct jest tutaj prymitywem wykonawczym, a nie trzecią ścieżką.
Ta implementacja nie ma replannera: wykonuje początkowy plan do końca. Dodanie replanningu wymagałoby krawędzi z executor z powrotem do planner oraz reguły określającej, kiedy zaskakujący wynik uzasadnia kolejne wywołanie modelu.
LangGraph utrzymuje deklaratywny sposób definiowania połączeń:
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"],
)
Automatyczny wybór wzorca za pomocą routera
Router mapuje charakter żądania na odpowiednią ścieżkę. Schema-Guided Reasoning ogranicza dane wyjściowe klasyfikatora:
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)
Dzięki temu routerowi „current price” trafia do ReWOO, a „investment thesis” do Plan-and-Execute. W przypadku niejednoznacznego żądania domyślnie wybierany jest szczegółowy research. Zanim udostępnisz router użytkownikom, porównaj obie ścieżki w ramach stałego workflow, używając tych samych zadań, narzędzi, dowodów i budżetu. Powtarzaj próby stochastyczne i zliczaj każdą rozpoczętą próbę. Śledź skuteczność zadań, błędną akceptację, tokeny z cache i spoza cache, czas ścienny oraz odzyskiwanie po błędach narzędzi lub mylących obserwacjach. Są to decyzje dotyczące przepływu sterowania, a nie ranking sposobów wdrożenia; relacja inżynierska firmy Anthropic również zaleca rozpoczynanie od prostych, kompozycyjnych workflowów.
Pełna implementacja towarzysząca, obejmująca router i współdzielony stan, znajduje się w przypiętym commicie Market Analyst Agent.
Kolejna warstwa to pamięć
Część 2, Architektura pamięci agentów AI, rozdziela wznawialne checkpointy od wiedzy między sesjami i dokumentów projektowych. Bez tej warstwy stanu opisane wyżej router i executor działają tylko tak długo, jak długo aktywne pozostają jeden proces i jedno okno kontekstu.
Referencje
- 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