Salta el contingut

4.2 ReAct i Chain-of-Thought

En Resum

ReAct (Reason + Act) i Chain-of-Thought (CoT) són dos patrons fonamentals que permeten als LLM raonar explícitament pas a pas, millorant dràsticament la qualitat en tasques complexes.


🧪 Chain-of-Thought (CoT): Raonar Pas a Pas

El Problema sense CoT

Els LLM fallen en tasques de raonament multi-pas quan se'ls demana una resposta directa:

❌ SENSE CHAIN-OF-THOUGHT:

Pregunta: "Si tenim 23 alumnes i 3 professors, 
          i cada grup ha de tenir el mateix nombre 
          d'estudiants i com a mínim 1 professor, 
          quants grups podem formar maximitzant la participació?"

Resposta LLM (directa): "Podem formar 3 grups." ← Incorrecte o sense justificació!

Amb CoT: El Model Mostra el Raonament

✅ AMB CHAIN-OF-THOUGHT (simplement afegint "Pensa pas a pas"):

Pregunta: "(...) Pensa pas a pas."

Resposta LLM:
1. Tenim 23 alumnes i 3 professors.
2. Cada grup necessita com a mínim 1 professor → màxim 3 grups.
3. Si fem 3 grups, cada professor supervisa 1 grup.
4. Distribució d'alumnes: 23 ÷ 3 = 7 grups de 7, un grup de 9 (23 = 7+7+9).
5. Per maximitzar la participació = distribuir el més igual possible.
6. 3 grups: 8 + 8 + 7 alumnes (la distribució més equitativa).
Resposta final: 3 grups de 7-8 alumnes, un professor per grup.

Tipus de CoT

Simplement afegir la frase màgica al final del prompt:

prompt = """
Resol aquest problema: {problema}

Pensa pas a pas.
"""
# O en anglès: "Let's think step by step."
# Funciona sorprenentment bé sense cap exemple!

Proporcionar exemples de raonament per guiar el model:

prompt = """
Exemple 1:
Problema: Hi ha 15 pomes i 3 persones. Quantes pomes per persona?
Raonament: 15 pomes ÷ 3 persones = 5 pomes per persona.
Resposta: 5 pomes.

Exemple 2:
Problema: ...
Raonament: ...

Ara resol: {problema}
Raonament:
"""

El model genera automàticament exemples de raonament:

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

# Primera crida: generar exemples de raonament
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)

examples_prompt = ChatPromptTemplate.from_template("""
Genera 3 exemples de problemes similars a: "{problema}"
Per a cada exemple, mostra el raonament pas a pas.
Format: Problema | Raonament pas a pas | Resposta
""")

# Segona crida: resoldre amb els exemples generats
solve_prompt = ChatPromptTemplate.from_template("""
{exemples}

Seguint el mateix patró de raonament, resol:
{problema}
Raonament pas a pas:
""")

🔄 ReAct: Raonament + Acció

El Framework ReAct

ReAct (Reasoning + Acting) és un patró publicat per Google Research al paper "ReAct: Synergizing Reasoning and Acting in Language Models" (Yao et al., 2022). La idea: alternar explícitament entre pensar (Thought) i actuar (Action/Observation).

Thought: Raonament intern del model
Action: Quina eina cridar i amb quins arguments
Observation: Resultat de l'eina
... (repetir fins que es pugui respondre)
Final Answer: Resposta definitiva
🔬 Bucle ReAct en Acció

Exemple Real d'un Agent ReAct

# Exemple de TRAÇA REAL d'un agent ReAct amb verbose=True
# pip install langchain langchain-openai wikipedia --break-system-packages

from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from langchain import hub

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

tools = [
    WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()),
]

prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = executor.invoke({
    "input": "Quina és la diferència d'edat entre Linus Torvalds "
             "i Guido van Rossum?"
})
> Entering new AgentExecutor chain...

Thought: Necessito saber l'any de naixement de Linus Torvalds 
         i de Guido van Rossum per calcular la diferència d'edat.
         Buscaré informació sobre Linus Torvalds primer.

Action: wikipedia
Action Input: Linus Torvalds

Observation: Linus Benedict Torvalds (born December 28, 1969) is a 
  Finnish-American software engineer who created the Linux kernel...

Thought: Linus Torvalds va néixer el 28 de desembre de 1969. 
         Ara busco Guido van Rossum.

Action: wikipedia  
Action Input: Guido van Rossum

Observation: Guido van Rossum (born January 31, 1956) is a Dutch 
  programmer best known as the creator of Python...

Thought: Guido van Rossum va néixer el 31 de gener de 1956. 
         Ara puc calcular la diferència d'edat:
         1969 - 1956 = 13 anys. 
         Però he de ser més precís: Torvalds va néixer al desembre,
         van Rossum al gener. Per tant, durant la major part de l'any,
         la diferència és de 13 anys.

Final Answer: La diferència d'edat entre Linus Torvalds (nascut el 28 
  de desembre de 1969) i Guido van Rossum (nascut el 31 de gener de 
  1956) és de 13 anys. Van Rossum és 13 anys més gran.

> Finished chain.

🌳 Tree of Thoughts (ToT): Explorar Múltiples Camins

Publicat per Princeton el 2023, ToT és una extensió de CoT que explora múltiples raonaments en paral·lel i selecciona el millor.

CoT:  Problema → Pensament1 → Pensament2 → Pensament3 → Resposta
               (Una sola cadena de raonament)

ToT:  Problema → Pensament1a → Pensament2a → Resposta A
                            → Pensament2b → Resposta B (seleccionada!)
              → Pensament1b → Pensament2c → Resposta C
              (Explora un arbre de possibilitats, selecciona la millor)
# Implementació simplificada de ToT amb LangChain
# (Versió conceptual — implementació real és més complexa)

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

llm = ChatOpenAI(model="gpt-4o", temperature=0.7)

def tree_of_thoughts(problem: str, n_thoughts: int = 3) -> str:
    """
    Genera múltiples raonaments per a un problema 
    i selecciona el millor.
    """

    # Pas 1: Generar múltiples pensaments inicials
    generate_prompt = ChatPromptTemplate.from_template("""
    Problema: {problem}

    Genera {n} enfocaments completament DIFERENTS per resoldre'l.
    Per a cada enfocament, explica la idea bàsica en 2 frases.
    Format: ENFOCAMENT 1: ... | ENFOCAMENT 2: ... | ENFOCAMENT 3: ...
    """)

    thoughts_response = llm.invoke(
        generate_prompt.format_messages(problem=problem, n=n_thoughts)
    )

    # Pas 2: Avaluar i seleccionar el millor pensament
    evaluate_prompt = ChatPromptTemplate.from_template("""
    Problema: {problem}

    Enfocaments proposats:
    {thoughts}

    Avalua cada enfocament (viabilitat, completesa, simplicitat).
    Selecciona el millor i desenvolupa'l completament.
    Explica per qué l'has seleccionat.
    """)

    final_response = llm.invoke(
        evaluate_prompt.format_messages(
            problem=problem, 
            thoughts=thoughts_response.content
        )
    )

    return final_response.content

# Exemple d'ús
result = tree_of_thoughts(
    "Dissenyar un sistema per detectar i alertar sobre failures "
    "en microserveis sense degradar el rendiment."
)
print(result)

📊 Comparació dels Patrons de Raonament

Patró Quan Usar Avantatges Inconvenients
Directe Preguntes simples i factuals Ràpid, barat Falla en raonament complex
Zero-Shot CoT Tasques de raonament generals Simple, eficaç Pot generar raonaments erronis
Few-Shot CoT Tasques amb patrons repetitius Alta qualitat, consistent Requereix exemples manuals
ReAct Tasques que requereixen informació externa Pot cercar dades actuals Més lent, usa més tokens
ToT Problemes complexos amb múltiples solucions Explora alternatives Molt car en tokens

💻 Implementació Avançada: Agent ReAct amb Múltiples Eines

# Agent complet amb múltiples eines i ReAct
# pip install langchain langchain-openai duckduckgo-search wikipedia python-dotenv

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import (
    DuckDuckGoSearchResults,
    WikipediaQueryRun,
)
from langchain_community.utilities import WikipediaAPIWrapper
from langchain.tools import tool
from langchain import hub
import math

load_dotenv()  # Carrega OPENAI_API_KEY del fitxer .env

# ── EINES PERSONALITZADES ─────────────────────────────────────

@tool
def calculadora(expressio: str) -> str:
    """
    Calcula expressions matemàtiques.
    Útil per a sumes, restes, multiplicacions, divisions, potències.
    Exemples: '25 * 4', 'sqrt(144)', '2**10'
    """
    try:
        # Atenció: eval() amb expressions de l'usuari és insegur en producció!
        # Usar una llibreria com numexpr o sympy en entorns reals.
        resultat = eval(expressio, {"__builtins__": {}}, {"sqrt": math.sqrt, "pi": math.pi})
        return f"Resultat: {resultat}"
    except Exception as e:
        return f"Error en el càlcul: {str(e)}"

@tool  
def data_actual(query: str) -> str:
    """
    Retorna la data i hora actuals.
    Útil quan cal saber quina data és avui.
    """
    from datetime import datetime
    now = datetime.now()
    return f"Data i hora actuals: {now.strftime('%A, %d de %B de %Y, %H:%M')}"

# ── CONFIGURACIÓ DE L'AGENT ────────────────────────────────────

llm = ChatOpenAI(
    model="gpt-4o",
    temperature=0,
    max_tokens=2000
)

tools = [
    DuckDuckGoSearchResults(name="cerca_web"),
    WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()),
    calculadora,
    data_actual,
]

# Prompt personalitzat que indica la llengua
prompt = hub.pull("hwchase17/react")

agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    max_iterations=8,
    handle_parsing_errors=True,
    early_stopping_method="generate"
)

# ── TASQUES DE PROVA ──────────────────────────────────────────

tasques = [
    "Quants dies han passat des de la creació de Linux fins avui?",
    "Quin és el logaritme en base 2 de 1024?",
    "Quin és el país amb més habitants del món i quina és la seva capital?",
]

for tasca in tasques:
    print(f"\n{'='*60}")
    print(f"📝 TASCA: {tasca}")
    print('='*60)
    result = executor.invoke({"input": tasca})
    print(f"\n✅ RESPOSTA FINAL: {result['output']}")

✅ Activitats de Consolidació

Exercici 4.2.1 — Observar el ReAct

Executa l'exemple del "Agent ReAct Complet" amb verbose=True i analitza:

  1. Quantes iteracions (Thought → Action → Observation) ha necessitat?
  2. En algun moment el model ha comès un error de raonament? Com l'ha corregit?
  3. Ha usat la calculadora quan hauria pogut usar la cerca web o viceversa?

Exercici 4.2.2 — Afegir una Eina Pròpia

Afegeix almenys dues eines noves a l'agent:

  1. Una eina que llegeixi un fitxer de text local
  2. Una eina que retorni el temps actual d'una ciutat via API (p.ex. wttr.in)

Prova l'agent amb tasques que requereixin usar ambdues eines.

Exercici 4.2.3 — Comparació CoT vs Directe

Per a les tasques següents, compara la resposta del model (a) directament i (b) amb "Pensa pas a pas":

  1. "Un tren va de A a B a 120km/h. La distància és 360km. Si surt a les 9:00, a quina hora arriba?"
  2. "Si tinc 100€ i faig 3 inversions successives amb guany del 10% cada una, quant tinc al final?"

Documenta si CoT millora la precisió i per qué.


📚 Referències

  • Yao, S. et al. (2022). "ReAct: Synergizing Reasoning and Acting in Language Models". arXiv:2210.03629
  • Wei, J. et al. (2022). "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models". arXiv:2201.11903
  • Yao, S. et al. (2023). "Tree of Thoughts: Deliberate Problem Solving with Large Language Models". arXiv:2305.10601