JJoeven

Projects/ReAct Research Agent/Part 3

The ReAct Loop

Parse Thought/Action/Action Input, run search and open, cap steps, and finish with an answer object including citations.

This part is the runtime: parse ReAct text → tool → observation → repeat. You will keep a fake model that emits ReAct strings so the parser is real. A production LLM will emit the same shape if you prompt it that way (or you will use native tool calls and skip the poetry — still keep the parser tests).

The ReAct grammar (strict)

text
Thought: <one line>
Action: search|open|finish
Action Input: <string or JSON>

Rules you should enforce:

  • All three labels present, in order.
  • Action is an allowlisted name.
  • For search, Action Input is a query string.
  • For open, Action Input is a URL string.
  • For finish, Action Input is JSON: {"answer": "...", "citations": ["..."], "cannot_answer": false}.

If the model uses Final Answer: instead of finish, reject it. One protocol. Your weather project already taught you that kindness in parsers becomes ambiguity in production.

Fake policy for the happy path

For Who founded Acme Robotics?:

  1. Thought: need to search.
  2. Action search Acme Robotics founded.
  3. Observation: hits include wiki/acme.
  4. Thought: open the wiki.
  5. Action open that URL.
  6. Thought: fact is in the body.
  7. Action finish with Ada Ortiz, 2014, citations [that url].

Implement this as a state machine keyed off opened and searched flags, not as a recorded script only — so a second question can work with the same function.

Live Pythonpython
Output
Run to execute this in your browser. Nothing is sent to a server.

Step-by-step loop details

  1. Prompt the policy with the question, tool docs, and ReAct format reminder.
  2. Parse strictly. On failure, same parse-retry pattern as the weather agent.
  3. Execute search / open / finish.
  4. Append observation without the thought if you are token-poor; keep thoughts in a side trace.
  5. On finish, do not trust citations yet — part 4 validates.
  6. Hit max_steps → cannot-answer, not a guessed paragraph.

Max steps is a product feature

Research feels unbounded. It is not. Six tool calls is plenty for a 4-page web. If the model is still searching, it is stuck. Convert stuckness into abstention. Users prefer "I could not find this" to a confident wrong founder.

Thoughts in the transcript

If you send thoughts back into the next prompt, the model may imitate bad reasoning. Some teams strip thoughts from the next call and only keep actions+observations. Try both in part 5. For now, keep them for debugging in print.

Watch out:Never eval Action Input. JSON parse for finish; strings for search/open.

Exercise

Change the fake model so it tries to finish after search without open. Let the loop still run. In part 4 you will reject that finish for missing opens. Observe the bad output today so you know why the check exists.

Check your understanding

When max_steps is hit, what should the research agent do?