Roadmap / Agents & Multi-Agent Systems

✓

A ReAct Agent Loop

mediumagentstools

The problem

ReAct (a name that joins "reasoning" and "acting") is the basic loop of an agent. At each step the model writes some text: a Thought, then either an Action (a call to a tool) or a Final Answer. A program runs the tool and writes an Observation, which is added to the transcript, and the model is called again on the longer transcript. You will write this loop. The model is a function llm(prompt) -> text, in the tests a scripted fake.

The loop

The transcript starts as ["Question: " + question]. At each step:

  1. Call llm("\n".join(transcript)). It returns the model's next text, and you append it to the transcript as it is.
  2. If the text contains a line starting with Final Answer:, return (answer, transcript), where the answer is the rest of that line, stripped.
  3. Otherwise look for a line Action: <tool>[<input>] (the input is everything between the first [ and the last ]):
    • if there is none, append "Observation: Invalid format. Use Action: tool[input] or Final Answer: ...";
    • if <tool> is not in tools (a dict of name to function), append "Observation: Unknown tool <tool>";
    • otherwise call the tool with the input string and append "Observation: " + str(result). If the tool raises an exception, append "Observation: Error: " + str(exc) instead: the model should see the error and try again.
  4. After max_steps calls to llm without a final answer, return (None, transcript).

Example. tools = {"calc": eval-like function} and a model that replies, in turn, Thought: I need to multiply. + newline + Action: calc[12*7+3], and then Thought: Done. + newline + Final Answer: 87. The transcript at the end:

Question: What is 12*7+3?
Thought: I need to multiply.
Action: calc[12*7+3]
Observation: 87
Thought: Done.
Final Answer: 87

and the function returns ("87", transcript).

If the model asks for calc[1/0], the tool raises, and the transcript gets Observation: Error: division by zero. The model can then ask for something else: in the next step it writes Action: calc[2+2], gets Observation: 4, and answers.

What to implement

run_react(llm, tools, question, max_steps): return (answer, transcript), or (None, transcript) if there is no answer within max_steps.

Requirements

  • Never crash on bad model output or failing tools: every problem becomes an observation.
  • llm is called at most max_steps times.

Hint

re.search(r"^Action:\s*(\w+)\[(.*)\]\s*$", text, re.MULTILINE) matches the action line; the greedy (.*) keeps nested brackets inside the input. For the final answer, r"^Final Answer:(.*)$" with the same flag.

solution.py
Python idle

Press Run (⌘/Ctrl + Enter) to test your code.