🧙 Maestro Yoda Cap. 22 · Agenti, tool use, RAG

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.

un personaggio degli scacchi che alterna due mosse — pensare (nuvoletta “Thought: …”) e muovere (mano che sposta il pezzo, “Action: …”). Poi guarda il risultato (“Observation: …”) e ricomincia. Sotto: “il pattern Thought-Action-Observation, ripetuto a oltranza”

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:

  1. Manda al modello tutto fino a “Action:”.
  2. Il modello completa la riga Action.
  3. Il runtime parsa, esegue, scrive Observation.
  4. Re-manda al modello l’intero stream esteso.
  5. Il modello produce Thought successivo + Action.
  6. 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).

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.