Puntata 237
Puntata 237 — ReAct: ragionare e agire alternativamente
Livello: 🧙 Maestro Yoda · Capitolo 22 · Agenti, tool use, RAG
Nel 2022 Yao et al. (Princeton + Google) pubblicano “ReAct: Synergizing Reasoning and Acting in Language Models”. Diventa la “ricetta della nonna” degli agenti AI. Non l’unica ricetta, ma quella che si insegna per prima perché è elegante, leggibile, e funziona quasi sempre meglio del semplice tool use.
Da dove arriva ReAct
Pre-ReAct (2021-2022) gli LLM facevano una di due cose:
- Chain-of-Thought (CoT): ragionavano “ad alta voce” prima di rispondere. Bene per problemi di logica, male quando serve informazione esterna.
- Tool use diretto: chiamavano un tool, ricevevano risultato, rispondevano. Bene per recupero info, male per problemi multi-step.
Yao et al. (ICLR 2023) propongono di alternarli: il modello produce un blocco di reasoning (“Thought”) prima di ogni action, e analizza il risultato (“Observation”) prima di decidere il prossimo Thought.
Thought 1: Devo capire dove si trova X.
Action 1: search("dove si trova X")
Observation 1: X si trova in città Y.
Thought 2: Adesso devo capire chi è il sindaco di Y.
Action 2: search("sindaco di Y")
Observation 2: Il sindaco di Y è Z.
Thought 3: La risposta è Z.
Final answer: Z.
Esplicitare il reasoning aumenta drasticamente l’accuracy su task multi-step e rende il loop interpretabile: puoi leggere il pensiero del modello e capire dove ha sbagliato.
L’esperimento originale (2022)
Tre benchmark:
- HotpotQA: multi-hop QA su Wikipedia.
- FEVER: fact verification.
- ALFWorld + WebShop: simulazioni di azione in ambienti testuali.
Risultati paper:
- ReAct vs CoT puro: +10-20% accuracy su HotpotQA con prompt few-shot.
- ReAct vs Tool use diretto: +5-15% su task multi-step.
- ReAct su PaLM-540B: comparable a fine-tuned IL baseline su WebShop.
Il punto: non era un modello nuovo, era un prompting pattern. Stesso modello, prompt diverso, +15% accuracy.
Anatomia di un prompt ReAct
You are an agent that solves problems by alternating between Thought, Action, and Observation.
Available tools:
- search(query: str): search the web and return relevant snippets.
- calculate(expr: str): evaluate a math expression.
Follow EXACTLY this format:
Thought: [your reasoning about what to do next]
Action: [tool_name(args)]
Observation: [will be filled by the runtime]
... (repeat as needed)
Thought: I have enough information.
Final Answer: [your answer]
Question: {user_question}
Il runtime:
- Manda al modello tutto fino a “Action:”.
- Il modello completa la riga Action.
- Il runtime parsa, esegue, scrive Observation.
- Re-manda al modello l’intero stream esteso.
- Il modello produce Thought successivo + Action.
- Loop fino a “Final Answer:”.
ReAct vs function calling nativo
Nel 2023, ReAct era un prompt pattern senza supporto API. Si parsava il testo a mano.
Nel 2024-26, i provider espongono function calling nativo (puntata 236). Il “Thought” diventa il contenuto plain di un assistant message, l‘“Action” diventa un tool_call, la “Observation” diventa un tool message.
Anatomicamente è la stessa cosa. La differenza:
- Prompt-based ReAct: più flessibile, parser custom, lavora con qualsiasi modello.
- Native function calling: più affidabile, schema strict, supporto provider.
Pattern moderno (2026): function calling nativo per i tool, ma richiedi esplicitamente il “thought” nel system prompt:
“Before each tool call, briefly explain in 1-2 sentences why you’re calling this tool. After each tool result, briefly reflect on what you learned before deciding the next step.”
Questo è ReAct mascherato in una pipeline production.
Varianti e successori
ReAct è la base. Sopra si è costruito tanto.
Reflexion (Shinn et al., 2023)
Aggiunge un loop esterno di self-reflection. Dopo ogni episodio fallito, l’agente scrive una “lesson learned” che si auto-inietta nel prossimo tentativo. Migliora la convergenza su task ricorrenti.
Plan-and-Execute
L’agente fa un plan iniziale (sequenza di step ad alto livello) e poi esegue. Pensato per task lunghi dove decidere ogni step da capo è costoso. Esempio: LangGraph Plan-and-Execute, BabyAGI originale.
Tree-of-Thoughts (Yao et al., 2023)
Esplora multiple Thought in parallelo, valuta, sceglie il branch migliore. Stile MCTS leggero. Più accurato ma costoso (5-10× token).
LATS (Language Agent Tree Search)
Combina ToT + ReAct + RL-style value function. Stato dell’arte 2024 su molti benchmark agentici, ma overhead computazionale alto.
Reasoning models (o-series, R1) come agenti
DeepSeek-R1, o-series, Qwen 3 thinking: il “Thought” è prodotto nativamente in un blocco <thinking> interno, molto più ricco e lungo. Per agenti, spesso meglio di ReAct prompt-based su modelli non-reasoning. Caveat: latency e costo significativi.
ReAct in pratica con LangGraph
from langgraph.graph import StateGraph
from langgraph.prebuilt import ToolNode
from langchain_anthropic import ChatAnthropic
from langchain.tools import tool
@tool
def search(query: str) -> str:
"""Search the web for information."""
return tavily_search(query)
@tool
def calculate(expression: str) -> str:
"""Evaluate a math expression."""
return str(eval(expression))
tools = [search, calculate]
model = ChatAnthropic(model="claude-4-sonnet").bind_tools(tools)
def call_model(state):
response = model.invoke(state["messages"])
return {"messages": [response]}
def should_continue(state):
last = state["messages"][-1]
return "tools" if last.tool_calls else "end"
workflow = StateGraph(dict)
workflow.add_node("agent", call_model)
workflow.add_node("tools", ToolNode(tools))
workflow.add_edge("tools", "agent")
workflow.add_conditional_edges("agent", should_continue)
workflow.set_entry_point("agent")
app = workflow.compile()
result = app.invoke({"messages": [HumanMessage("Quanto fa la radice quadrata della popolazione di Roma?")]})
Sotto il cofano: implementa il loop ReAct con function calling nativo Anthropic, gestione tool execution, terminating condition.
Tipici problemi (e fix)
a) Loop infinito
L’agente continua a chiamare tool senza fermarsi.
- Fix: hard cap (max 10 iterations). Forced exit con “summarize what you found so far”.
b) Hallucinated tool name
Il modello inventa un tool che non esiste.
- Fix: response_format strict, validazione lato runtime, error message “Tool X does not exist. Available: […]” reinjected.
c) Thought sproporzionato all’Action
Il modello scrive paragrafi di thought e poi fa una micro-action.
- Fix: prompt “Keep thoughts to 1-2 sentences. Be direct.”
d) Skip del Thought (action diretta)
Modelli rapidi a volte saltano subito a Action senza Thought.
- Fix: regex check, se manca Thought reinjecta “Wait, what is your reasoning first?“
e) Wrong tool selection
L’agente sceglie ripetutamente il tool sbagliato.
- Fix: rivedi descrizioni tool (puntata 236). Spesso il problema è là, non nel reasoning.
f) Context bloat
Storico cresce, costo per request schizza.
- Fix: memory compression (riassumi turni vecchi), tool retrieval, eliminazione di tool result rumorosi.
Quando NON usare ReAct
Anti-pattern:
- Task in 1 step: “traduci questa frase” non ha bisogno di Thought-Action. Diretto.
- Workflow strettamente deterministico: se sai il flusso esatto, non delegare la scelta al modello. Hard-code.
- Real-time latency-critical: ReAct è verbose, costa secondi extra.
- Modelli piccoli (<7B): spesso non hanno la coerenza per loop multi-step. Meglio pipeline fissa.
ReAct sui modelli reasoning (R1, o-series)
Nel 2025-26 i modelli reasoning hanno cambiato il quadro:
- Il “Thought” è prodotto nativamente in un blocco interno (thinking), molto lungo.
- Per agenti: spesso un singolo prompt + reasoning model + tool calls native batte un ReAct loop su modello base.
- Caveat: i thinking block costano token (anche se nascosti all’utente finale). Su task agentici lunghi: 10-50× costo vs un Llama base in ReAct.
Pattern emergente 2026: reasoning model per la decisione strategica (cosa fare) + modello rapido (Haiku, GPT-mini) per esecuzione tattica (chiamate tool semplici, riformulazione). Riduce costo mantenendo accuracy.
Glossario lampo
- ReAct — pattern Thought / Action / Observation in alternanza, introdotto da Yao et al. (Princeton, 2022).
- CoT (Chain-of-Thought) — pattern in cui l’LLM produce reasoning prima della risposta. ReAct ne è l’estensione “con tool”.
- Reflexion — pattern di self-reflection iterativa post-fallimento. Aggiunge memoria di lezioni apprese.
- Plan-and-Execute — variante in cui un plan ad alto livello viene creato all’inizio e poi eseguito step-by-step.
- Tree-of-Thoughts — esplora branch multipli di reasoning in parallelo, sceglie il migliore. Più accurato, più costoso.
Take-away
ReAct è la “ricetta della nonna” degli agenti: alterna Thought e Action, registra l’Observation, ripeti. Funziona perché esplicita il reasoning e lo rende ispezionabile. Nel 2026 è meno un prompt pattern e più una forma mentis: i modelli reasoning fanno Thought nativamente, i framework moderni implementano il loop, ma il principio resta — non delegare cieca esecuzione, fai pensare il modello prima di ogni mossa.
➡️ Prossima puntata: RAG — quando il modello non sa, fa retrieval. Quando serve davvero e quando è overkill.