Loops de raciocínio de AI agents: ReAct, ReWOO e Plan-and-Execute
Tradução automática Este artigo foi traduzido automaticamente a partir da versão original em inglês.
Um agent reasoning loop é o fluxo de controlo que decide quando um modelo planeia, chama uma tool, lê o resultado e termina. Para um engenheiro que está a construir um agent, essa escolha é também um orçamento: determina com que frequência o modelo é executado, quanto histórico acompanha cada chamada e se um resultado inesperado pode alterar a ação seguinte.
Este artigo compara ReAct, ReWOO e Plan-and-Execute através de um LangGraph Market Analyst Agent que construí. No final, terá uma regra de routing e estruturas de implementação que poderá adaptar, em vez de apenas três nomes para acrescentar a um diagrama.
O loop é a camada mais interna desta série. A memória, as tools, a segurança, o runtime e as verificações de aceitação envolvem-no; não o substituem.
Para uma comparação breve de frameworks, consulte Best AI Agent Frameworks in 2026.
O reasoning loop decide o que fazer a seguir. Não armazena estado, executa tools nem autoriza efeitos secundários.
O harness é o programa de controlo entre o modelo e a máquina. Reúne prompts a partir do estado armazenado (Parte 2), define as ações que o modelo pode nomear (Parte 3), autoriza chamadas (Parte 4) e verifica as evidências antes de declarar uma tarefa concluída (Parte 6). São problemas de engenharia distintos, mas um turno passa pelos quatro.
O runtime (Parte 5) fornece o registo da sessão, o sandbox, o checkpoint store e os traces que sobrevivem a um único processo worker.
Cada artigo é autónomo. Em conjunto, avançam do loop para as camadas exteriores.
Comece pelo limite de falha
Um bom prompt não resolve a questão do fluxo de controlo. Os padrões diferem na quantidade de trabalho que é fixada antes da primeira chamada de uma tool. Isso determina o número de chamadas ao modelo, quando um plano incorreto se torna visível e se um resultado inesperado de uma tool pode redirecionar a execução.
Três padrões de raciocínio para AI agents
ReAct: decidir após cada observação
ReAct (Yao et al., 2022), abreviatura de Reason + Act, mantém a decisão seguinte próxima da observação mais recente:
- Thought: o agent gera um “thought” para decompor o objetivo e planear o passo seguinte.
- Action: com base no thought, chama uma tool.
- Observation: o agent lê o resultado, atualizando a sua compreensão para o thought seguinte.
Isto confere ao ReAct propriedades úteis:
- No exemplo manual do artigo, com PaLM-540B e HotpotQA, as observações da Wikipedia produziram menos factos alucinados do que o prompting com chain-of-thought.
- O agent pode alterar a estratégia em tempo real com base no que acabou de observar.
- O histórico de tool calls e observações fornece um execution trace concreto.
O mesmo loop tem custos:
- Numa implementação ingénua com o histórico completo, o histórico é reprocessado a cada passo, pelo que a latência e o custo crescem com o comprimento do loop. Summarization ou truncation podem limitar esse crescimento, à custa de contexto descartado.
- É pouco eficiente quando as tool calls poderiam ter sido planeadas antecipadamente — precisamente o espaço ocupado pelo ReWOO.
- Sem uma condição de paragem ou um limite de passos, o loop pode executar indefinidamente.
Use-o em tarefas exploratórias, debugging e trabalho em que não seja possível prever a ação seguinte.
ReWOO: compilar primeiro o tool graph
ReWOO (Reasoning WithOut Observation) separa o planeamento da execução. O planner escreve toda a sequência de tools numa única passagem, usando placeholders para valores que só existirão depois da execução.
- Plan: uma chamada ao LLM escreve o plano completo de tool calls, usando variable placeholders (
#E1,#E2) para outputs que ainda não existem. - Worker: um executor sem LLM executa as tools planeadas e preenche os placeholders. O worker do artigo segue o plano; a implementação apresentada mais adiante acrescenta batches paralelos sensíveis a dependências para passos prontos.
- Solver: uma chamada final ao LLM recebe as observações recolhidas e escreve a resposta.
Esta separação proporciona:
- Menos chamadas repetidas ao modelo do que ReAct quando o plano inicial permanece válido.
- Menos histórico de prompts repetido do que num loop intercalado com histórico completo. A latência das tools continua a depender da forma como o worker agenda as chamadas.
- O planner pode ser fine-tuned de forma independente, sem um ambiente em tempo real.
Também cria um limite rígido. No teste de stress HotpotQA do artigo, todas as tools
devolveram No evidence found; o ReWOO perdeu menos precisão do que o ReAct porque as
observações falhadas não fizeram o planner entrar noutro loop. Trata-se de robustez
relativa, não de uma política de recuperação durante a execução. Uma implementação
continua a ter de decidir se um erro de uma tool passa a ser evidência para o solver,
desencadeia um retry ou aborta a execução. O ReWOO adequa-se a workflows previsíveis;
não faz re-plan em torno de um graph inicial incorreto por si só.
Use-o para snapshots rápidos, verificações de estado e dashboards cujo comportamento das tools seja previsível.
Plan-and-Execute: decompor e depois reagir localmente
Plan-and-Solve prompting descreve um método de prompting que começa por criar um plano e depois resolve as subtarefas. Um padrão relacionado de tool orchestration é habitualmente designado por Plan-and-Execute. O guia Plan-and-Execute da LangChain documenta esse padrão:
- Planning phase: o agent gera primeiro um plano que divide a tarefa em subtarefas mais pequenas.
- Execution phase: o agent executa depois essas subtarefas, uma de cada vez. Quando há tools envolvidas, cada subtarefa é normalmente executada como um pequeno loop ReAct próprio, pelo que o executor continua a poder reagir ao resultado de uma tool, embora o plano global seja fixo.
O artigo original centrou-se em prompting zero-shot. Numa implementação com uso de tools, o padrão de orchestration pode executar sequencialmente os passos planeados e usar modelos diferentes para planeamento e execução. Essa divisão de modelos é uma escolha de implementação, não um resultado estabelecido pelo artigo Plan-and-Solve.
O padrão é útil porque oferece:
- Raciocínio hierárquico que reflecte a forma como um perito humano decompõe um projeto.
- Uma edge explícita de replanning pode pausar e reavaliar depois de um resultado inesperado num passo.
- Especialização de modelos. O planner pode ser dispendioso e o executor económico.
- Com um checkpointer configurado, cada passo concluído pode tornar-se num ponto de retoma.
Os custos são:
- Mais round trips ao modelo do que ReWOO quando cada passo contém o seu próprio loop ReAct.
- Mais estado para gerir.
- É excessivo para queries de uma só execução.
Use-o para análises complexas e research que exijam uma síntese final.
Escolha com base no ponto em que o plano pode falhar
| Funcionalidade | ReAct (2022) | Plan-and-Execute (2023) | ReWOO (2023) |
|---|---|---|---|
| Filosofia central | Improviser: decide o passo seguinte a partir do último resultado, uma chamada de cada vez. | Architect: constrói um blueprint completo, executa-o e revê-o. | Optimizer: compila um dependency graph e agrupa as chamadas que estão prontas. |
| Workflow | Loop iterativo: Thought → Action → Observation. | Duas fases: Fase 1 (Planning), Fase 2 (Execution). | Desacoplado: o Planner escreve um graph de tool calls; o Worker executa-as; o Solver compõe a resposta. |
| Adaptabilidade | Máxima: pode mudar de direção depois de cada tool call. | Média: normalmente só faz re-planning depois de concluído um conjunto de passos. | Mínima: o script do planner é executado até ao fim; nada faz re-planning a meio da execução. |
| Eficiência | Um loop ingénuo com histórico completo repete mais input tokens; a gestão de contexto pode limitar esse crescimento. | O re-planning é ocasional, em vez de ocorrer a cada passo, e o loop ReAct de cada passo pode começar com um contexto curto, em vez do histórico completo da execução. | Menos chamadas ao modelo; o worker deste artigo também agrupa tools prontas segundo as dependências. |
| Mais indicado para | Exploração aberta ou tarefas com resultados imprevisíveis. | Tarefas de horizonte longo que exigem um objetivo estável (por exemplo, escrever um artigo). | Workflows estruturados e repetíveis (por exemplo, consultar o tempo em 5 cidades). |
A tabela é um apoio ao routing, não um benchmark. Use ReAct quando uma observação puder alterar a ação seguinte. Use Plan-and-Execute quando a tarefa se decompor de forma clara, mas cada passo ainda precisar de feedback. Use ReWOO quando todas as dependências entre tools forem conhecidas antes da execução. Na implementação didática abaixo, esse dependency graph permite batches paralelos e permite ao worker detetar um graph que não consegue avançar. O seu helper execute_tool usa deliberadamente uma estratégia fail-fast; o código de produção precisa de uma política explícita de retry, fallback ou tratamento do erro como evidência. Avalie os três com o seu modelo, a latência das tools, o conjunto de tarefas e a política de retries antes de otimizar o número de chamadas.
Um exemplo completo: o Market Analyst Agent
O Market Analyst Agent torna a distinção concreta. Uma única codebase usa os três padrões para market research, e um router escolhe entre um percurso de deep research e um percurso de flash briefing. Os excertos abaixo são variantes didáticas abreviadas do commit b4e769a: o companion pinned captura uma exceção de uma tool e passa uma string de erro ao solver, enquanto o worker mostrado aqui deixa essa exceção abortar e lança um erro quando o seu dependency graph não consegue avançar. O route e o state shape são os mesmos; a política de falhas é deliberadamente explícita aqui.
Usa LangGraph para orchestration. Um node é uma função Python que devolve campos a atualizar no estado partilhado. Uma edge declara o node seguinte e pode chamar uma função de routing. O LangGraph funde as atualizações e pode criar checkpoints depois de cada node, tornando uma execução retomável. Os três padrões partilham um único objeto de estado, pelo que o routing não exige três schemas separados:
O diagrama isola o routing e a criação do draft. Omite o evaluator partilhado e o publish gate apresentados mais adiante, para que os dois reasoning loops permaneçam legíveis.
Definição do estado
O state schema transporta os campos de que ambos os modos necessitam:
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
Padrão 1: implementação de Plan-and-Execute
Plan-and-Execute adequa-se a sínteses com vários passos. Um planner escreve os passos de alto nível; depois, um loop ReAct executa cada passo e reage aos resultados das tools.
O código abaixo fixa claude-sonnet-4-5-20250929, que era o que eu usava para executar estes exemplos quando este artigo foi publicado, em janeiro de 2026. As gerações dos modelos entretanto mudaram. Substitua-o pelo que estiver atualmente disponível e volte a verificar o comportamento do routing nas suas próprias tarefas — o importante é o padrão, não o model ID.
A implementação mantém quatro limites visíveis:
- Uma única planning phase inicial. Uma chamada ao LLM produz o plano completo como uma lista de descrições de passos.
- Output orientado por schema, que valida o plano antes da execução.
- Ainda não há execução de tools. O planner decide apenas o que fazer, não como fazê-lo.
- Passos legíveis por humanos. Cada passo é texto que um executor irá interpretar.
# 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
}
A linha llm.with_structured_output(PlanOutput) corresponde a Schema-Guided Reasoning (SGR), que abordei num artigo anterior. O schema permite à aplicação rejeitar um plano malformado antes de o LangGraph usar os campos validados para conduzir conditional edges.
Padrão 2: execução ReAct
Assim que o plano existe, o executor executa cada passo como o seu próprio loop ReAct. Esta é a Fase 2: cada passo é suficientemente pequeno para que um ciclo Thought-Action-Observation se mantenha focado, e o agent pode reagir ao que quer que a tool devolva.
A componente ReAct organiza-se assim:
- Execução iterativa. Um passo de cada vez, com feedback das observações.
- O loop Thought-Action-Observation é executado dentro de
create_react_agent. - Os resultados dos passos anteriores são fornecidos como contexto para o raciocínio atual.
- O agent escolhe as tools com base na descrição do passo.
- Pode alterar a abordagem a meio do passo, com base no resultado de uma tool.
# 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 factory name does not change the Thought-Action-Observation loop:
# 1. Agent generates a "thought" about what tool to call
# 2. Agent calls the tool ("action")
# 3. Tool returns result ("observation")
# 4. Agent decides: call another tool or finish
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,
}
Padrão 3: ReWOO para snapshots rápidos
Para um briefing rápido, o ReWOO remove as chamadas ao modelo da execution phase. As tools independentes são executadas em paralelo; as tools dependentes aguardam os seus pré-requisitos. O planner emite o tool graph antecipadamente e o worker executa-o sem perguntar ao modelo o que deve fazer a seguir.
A estrutura é a seguinte:
- Três fases (Planner → Worker → Solver), sem loops.
- As tool calls referenciam os placeholders
#E1,#E2para resultados que ainda não existem. - Não há LLM durante a execução. O worker limita-se a executar as tools.
- As tools independentes são executadas em paralelo.
- Uma única chamada de síntese no final, sobre todos os dados em conjunto.
Fase 1: planner ReWOO (cria antecipadamente o graph completo de execução)
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 # Exact tool to call
tool_args: dict # May contain variable refs like {"price": "#E1"}
depends_on: list[str] = [] # For dependency ordering
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, we create EXACT tool calls that
the worker will execute blindly.
"""
llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)
# Schema-Guided Reasoning ensures valid tool call specifications
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"""),
])
# State update: Store the complete execution plan
# Worker will execute this without any LLM involvement
return {"rewoo_plan": result.steps}
Fase 2: worker ReWOO (executa as tools sem raciocínio de 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])}
Fase 3: solver ReWOO (sintetiza todos os resultados numa única chamada ao 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())}
As três fases têm um contrato simples: o planner cria chamadas executáveis, o worker preenche os placeholders e o solver recebe os resultados completos. Um graph que não possa ser resolvido gera um erro antes da execução do solver; uma política de retry do LangGraph ou uma failure edge pode então decidir se deve fazer re-planning. Este contrato só é eficiente quando os pressupostos do planner resistem ao contacto com as tools.
Onde cada padrão chama o modelo
| Padrão | Chamadas ao LLM durante a execução | Atualizações de estado | Padrão de código principal |
|---|---|---|---|
| Plan-and-Execute | 1 para o planeamento + um loop ReAct por passo (várias chamadas cada um) + 1 para o relatório | Conclusão sequencial dos passos | planner_node() → loop: executor_node() → reporter_node() |
| ReAct (dentro de cada passo) | Várias por passo (ciclos thought-action) | Histórico acumulado de mensagens | O companion pinned usa create_react_agent(), agora deprecated |
| ReWOO | 1 para o planeamento + 0 durante a execução + 1 para a síntese | Batches de tools sensíveis a dependências | rewoo_planner_node() → rewoo_worker_node() → rewoo_solver_node() |
A diferença importante está no output do planner. É ele que determina quanta discricionariedade resta ao executor:
-
Plan-and-Execute cria descrições de passos legíveis por humanos:
# 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 ]O executor lê cada descrição e decide que tools deve chamar. É flexível, mas cada passo é o seu próprio loop ReAct, pelo que um passo custa várias chamadas ao modelo, não uma.
-
O ReAct não tem um plano inicial. Usa raciocínio iterativo:
# No planning phase - ReAct works step-by-step with accumulated messages messages = [ HumanMessage(content="Execute Step 1: Get current price"), AIMessage(content="I'll call get_stock_snapshot"), ToolMessage(tool_call_id="1", content="$132.45"), AIMessage(content="Now I need metrics..."), # ... agent continues until step complete ]Nesta implementação com histórico completo, cada chamada ao modelo volta a ler um histórico que cresce durante toda a tarefa. Um loop de produção pode fazer summarization ou truncation. O Plan-and-Execute também executa loops ReAct, mas cada um começa pela descrição desse passo e por um resumo das conclusões anteriores, não pelo histórico completo de tool calls.
-
O ReWOO cria especificações explícitas e executáveis de tool calls:
# Planner output (list of ReWOOPlanStep objects) rewoo_plan = [ ReWOOPlanStep( step_id="#E1", tool_name="get_stock_snapshot", tool_args={"ticker": "NVDA"} ), ReWOOPlanStep( step_id="#E2", tool_name="search_news", tool_args={"query": "NVDA earnings", "limit": 5} ), # ... all tool calls planned upfront ]O worker executa de forma cega, sem intervenção do LLM. Todas as chamadas ao modelo estão no planner e no solver, o que torna previsível o número de chamadas ao modelo.
Fluxo de memória e estado:
- Plan-and-Execute: o estado passa por
plan→current_step_index→research_data. - ReAct: o estado acumula-se no array
messages(o histórico completo da conversa). - ReWOO: o estado passa por
rewoo_plan, com os camposresultpreenchidos pelo worker.
Ligar os dois routes num único graph
O graph tem dois routes expostos ao utilizador sobre um único AgentState: o deep research usa Plan-and-Execute com um loop ReAct dentro de cada passo, enquanto o flash briefing usa ReWOO. O ReAct é aqui uma primitive de execução, não um terceiro route.
Esta implementação não tem replanner: executa o plano inicial até ao fim. Acrescentar replanning exigiria uma edge de executor de volta a planner e uma regra para determinar quando um resultado inesperado justifica outra chamada ao modelo.
O LangGraph mantém a ligação declarativa:
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 converge on the same acceptance check
builder.add_edge("evaluator", "publish")
builder.add_edge("publish", END)
return builder.compile(
checkpointer=checkpointer,
# Human-in-the-loop pause: the reporter (or the ReWOO solver) writes a
# draft, a fresh-context evaluator — a second model session with no
# history of the run — votes on it, and the graph stops before publish.
# The human approves a report that already carries an evaluator's
# verdict rather than adjudicating raw research. The verdict is
# advisory here — the graph routes to the interrupt either way.
# Part 6 takes up how an acceptance check decides that a run is done.
interrupt_before=["publish"],
)
Seleção automática do padrão com um router
O router mapeia o formato do pedido para o route. O Schema-Guided Reasoning restringe o output do classifier:
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)
Com este router, “current price” é encaminhado para ReWOO e “investment thesis” para Plan-and-Execute. O default é deep research quando o pedido é ambíguo. Antes de colocar o router diante dos utilizadores, reproduza um conjunto de tarefas através de ambos os routes e compare as chamadas ao modelo, o tempo total, as falhas e o comportamento de recuperação.
A implementação completa do companion, incluindo o router e o estado partilhado, está no commit fixado do Market Analyst Agent.
A camada seguinte é a memória
A Parte 2, AI Agent Memory Architecture, separa checkpoints retomáveis do conhecimento entre sessões e dos documentos de projeto. Sem essa camada de estado, o router e o executor acima só funcionam enquanto um único processo e uma única context window permanecerem ativos.
Referências
- 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