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:
- Call
llm("\n".join(transcript)). It returns the model's next text, and you append it to the transcript as it is. - If the text contains a line starting with
Final Answer:, return(answer, transcript), where the answer is the rest of that line, stripped. - 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 intools(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.
- if there is none, append
- After
max_stepscalls tollmwithout 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 thenThought: 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: 87and the function returns
("87", transcript).If the model asks for
calc[1/0], the tool raises, and the transcript getsObservation: Error: division by zero. The model can then ask for something else: in the next step it writesAction: calc[2+2], getsObservation: 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.
llmis called at mostmax_stepstimes.
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.