Accueil / Articles / ReAct à partir de zéro : pensée, action, pause, observation sans cadre préétabli

ReAct à partir de zéro : pensée, action, pause, observation sans cadre préétabli

Créez manuellement un petit cycle Groq ReAct — en commençant par des observations directes, puis en effectuant des appels d’outils pilotés par des expressions régulières — afin de comprendre pourquoi les frameworks d’agents modernes fonctionnent de la manière dont ils le font.

1373 mots

Introduction

Demandez à un modèle de langage simple le prix en temps réel d’Nvidia et le nombre d’actions que 100 000 $ permettent d’acheter. Sans outils, il invente une cote obsolète à partir des données d’entraînement, puis effectue les calculs correctement mais avec des entrées erronées. Il ne dispose d’aucune fonction intégrée signifiant « Je ne sais pas — vérifiez », car la recherche n’est pas une opération unique.

Au fur et à mesure que la qualité des réponses s’améliorait, les modèles se sont avérés utiles pour le raisonnement en une seule étape. La planification seule ne garantit pas encore l’autonomie. Les questions qui définissent les agents sont : qu’est-ce qui se trouve en dehors des connaissances du modèle, quelle action permet d’y accéder, comment obtenir un résultat, comment réviser le plan, et quand s’arrêter. La séquence décision → action → vérification → réplanification constitue un boucle, et non une ligne droite. C’est cette boucle qui est à l’origine des agents d’intelligence artificielle modernes. ReAct (Raisonnement + Action) a établi ce modèle. Les démonstrations initiales n’étaient pas des applications LangChain : il s’agissait d’un LLM, d’une instruction structurée, ainsi que d’une personne (ou plus tard un script) qui exécutait des actions et renvoyait les observations obtenues.

Découvrez ReAct : Raisonnement + Action

Un exemple concret, étape par étape :

from groq import Groq
import re
from dotenv import load_dotenv

_ = load_dotenv()
client = Groq()
message = client.chat.completions.create(
    model="openai/gpt-oss-120b",  # free, fast open-weight model hosted on Groq
    max_tokens=1000,
    messages=[
        {"role": "user", "content": "Hello, GPT!"}
    ],
)
print(message.choices[0].message.content) # Check the client
class Agent:
    def __init__(self, system=""):
        self.system = system
        self.messages = []
        if self.system:
            self.messages.append({"role": "system", "content": system})

    def __call__(self, message):
        self.messages.append({"role": "user", "content": message})
        result = self.execute()
        self.messages.append({"role": "assistant", "content": result})
        return result

    def execute(self):
        response = client.chat.completions.create(
            model="openai/gpt-oss-120b",
            max_tokens=1000,
            messages=self.messages,
        ).choices[0].message.content
        return response
prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.

Your available actions are:

calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary

fish_weight:
e.g. fish_weight: Shark
returns weight of a fish when given the breed

Example session:

Question: How much does a shark weigh?
Thought: I should look the fish weight using fish_weight
Action: fish_weight: Shark
PAUSE

You will be called again with this:

Observation: A Great white shark weights 41000 lbs

You then output:

Answer: A Great white shark weights 41000 lbs
""".strip()
def calculate(expression):
    return eval(expression)

def fish_weight(name):
    if "Shark" in name:
        return("Great white shark weighs 41000 lbs")
    elif "Puffer" in name:
        return("A puffer fish weighs 20 lbs")
    else:
        return("A fish can weight upto 47000 lbs")

known_actions = {
    "calculate": calculate,
    "fish_weight": fish_weight
}
abot = Agent(prompt)
result = abot("How much does a Shark weigh?")
print(result)
result = fish_weight("Shark")
result
next_prompt = "Observation: {}".format(result)
abot(next_prompt)
abot.messages # View the list of messages containing the conversation
# Present a compund query
abot = Agent(prompt)
# New query
question = """I have 2 fishes, a Shark and a puffer fish. \
What is their combined weight"""
abot(question)
next_prompt = "Observation: {}".format(fish_weight("Shark"))
print(next_prompt)
abot(next_prompt)
next_prompt = "Observation: {}".format(fish_weight("Puffer fish"))
print(next_prompt)
abot(next_prompt)
next_prompt = "Observation: {}".format(eval("41000 + 20"))
print(next_prompt)
abot(next_prompt)
## Add loop (automate the reasoning + act process)
action_re = re.compile('^Action: (\w+): (.*)
) # python regular expression to selection action
def query(question, max_turns=5):
    i = 0
    bot = Agent(prompt)
    next_prompt = question
    while i < max_turns:
        i += 1
        result = bot(next_prompt)
        print(result)
        actions = [
            action_re.match(a)
            for a in result.split('\n')
            if action_re.match(a)
        ]
        if actions:
            # There is an action to run
            action, action_input = actions[0].groups()
            if action not in known_actions:
                raise Exception("Unknown action: {}: {}".format(action, action_input))
            print(" -- running {} {}".format(action, action_input))
            observation = known_actions[action](action_input)
            print("Observation:", observation)
            next_prompt = "Observation: {}".format(observation)
        else:
            return
question = """I have 2 fishes, a Shark and a puffer fish. \
What is their combined weight"""
query(question)
  1. Importez le client Groq, re pour la correspondance de motifs, ainsi que load_dotenv ; chargez les variables d’environnement afin que la clé API ne soit pas codée en dur.
  2. Créez un client Groq qui lit GROQ_API_KEY depuis l’environnement.
  3. Envoyez un message simple « Hello » à un modèle configuré pour confirmer la connectivité.
  4. Construisez un Agent qui conserve une instruction système ainsi qu’un historique de messages en croissance : chaque appel ajoute le tour du utilisateur, envoie l’historique complet, ajoute la réponse du modèle, puis renvoie le texte.
  • Rédigez le prompt système qui impose la séquence Réflexion → Action → PAUSE → Observation.
  • Enregistrez des outils tels que calculate() pour les calculs arithmétiques et fish_weight() pour une petite table de référence dans known_actions.
  • Instanciez l’agent en utilisant le prompt système ReAct.
  • Demandez « Quel est le poids d’un requin ? » ; attendez une action proposée du type Action: fish_weight: Shark plutôt qu’un simple nombre.
  • Exécutez manuellement fish_weight("Shark") et conservez le résultat (d’abord en mode manuel).
  • Fournissez à nouveau Observation: … afin que l’agent puisse répondre.
  • Examinez abot.messages pour voir l’intégralité du texte que le modèle perçoit.
  • Démarrer un nouvel agent avec une question composite (poids du requin et du poisson-globe). 13–15. Fournissez manuellement chaque observation, y compris la somme totale si nécessaire.
  • Permettez à l’agent de synthétiser la réponse combinée une fois que des observations sont disponibles.
  • Ajoutez une expression régulière qui détecte automatiquement Action: name: input.
  • Encadrez la boucle dans query() : nouvel agent, envoyer la question, afficher la sortie, parser l’action, valider par rapport à known_actions, exécuter, observer, répéter jusqu’à obtenir la réponse finale ou atteindre la limite de tours.
  • Exécutez query() sur la question composée et suivez le processus Thought → Action → Observation → Answer sans étapes manuelles.
  • Conclusion

    Les agents partent d’une boucle, et non d’un cadre. Le Chain-of-thought a appris aux modèles à raisonner à voix haute ; ReAct structure le processus think–act–observe de la manière dont les humains travaillent. Les cadres d’agents populaires industrialisent cette idée. Construire manuellement la boucle modifie définitivement la perception que l’on a des cadres. L’article original se trouve à l’adresse https://arxiv.org/abs/2210.03629.

    La leçon pratique est que les frameworks cachent la boucle derrière les exécutants et les nœuds d’outils, mais le contrat reste identique : le modèle doit émettre une action pouvant être analysée, l’environnement de exécution ne doit exécuter que les outils autorisés, et les observations doivent réintégrer le transcript sous forme de messages de première classe. En omettant l’un de ces éléments, on obtient un chatbot présentant des effets secondaires, et non un agent.

    La leçon pratique est que les frameworks cachent la boucle derrière les exécutants et les nœuds d’outils, mais le contrat reste identique : le modèle doit émettre une action pouvant être analysée, l’environnement de exécution ne doit exécuter que les outils autorisés, et les observations doivent réintégrer le transcript sous forme de messages de première classe. En omettant l’un de ces éléments, on obtient un chatbot présentant des effets secondaires, et non un agent.

    La leçon tirée de l’industrie est que les frameworks cachent la boucle derrière les exécutants et les nœuds d’outil, mais le contrat reste identique : le modèle doit émettre une action pouvant être analysée, l’environnement de exécution ne doit exécuter que les outils autorisés, et les observations doivent réintégrer le transcript sous forme de messages de première classe. En omettant l’un de ces éléments, on obtient un chatbot présentant des effets secondaires, et non un agent.

    La leçon tirée de l’industrie est que les frameworks cachent la boucle derrière les exécutants et les nœuds d’outil, mais le contrat reste identique : le modèle doit émettre une action pouvant être analysée, l’environnement de exécution ne doit exécuter que les outils autorisés, et les observations doivent réintégrer le transcript sous forme de messages de première classe. En omettant l’un de ces éléments, on obtient un chatbot présentant des effets secondaires, et non un agent.