JJoeven

Curriculum/Prompting

Parseable Actions

ReAct is a format constraint. The action line is the product. Prefer JSON over ‘I will now Get-Job :)’.

intermediate20 min21 / 24

ReAct (reason + act) is a format for a loop, not a religion:

  1. Thought — optional scratchpad
  2. Action — a tool name
  3. Action Input — arguments
  4. Your code runs the tool (runtime track — not this lesson)
  5. Observation — stuffed back in as data
  6. Repeat until Final Answer (or a stop tool)

The thought is not the product. The action line is what your parser must get right 100% of the time. Prefer JSON:

{"thought": "...", "tool": "get_job", "args": {"job_id": 17}}

over free-form “I will now Get-Job with id seventeen :)” which will break on Tuesday.

ReAct is a loop
ThoughtActionObserveRepeat

The action line is the product. Thought is optional scratch.

ReAct is a loop

The LLM structured-output lesson is the validator. This lesson is the prompt: ask for that object, show one legal example, refuse prose. Unknown tools stop the loop. Format in text, enforcement in code.

Classic ReAct lines (Action: / Action Input:) still exist in blogs. If you still use them, parse them once, then convert to JSON internally. Do not regex the live site on Tuesday. Do not accept both dialects in production — that is two contracts.

What the prompt must teach

  • One object per turn (or one object per parallel call, each validated)
  • tool is an enum from the enabled list
  • args is an object, not a sentence
  • finish / handoff are tools too, not vibes in the thought
  • Thoughts that say “I called get_job” without a tool message are lies. The transcript is the truth

Partial application is how you refund and fail to log: one valid object and one prose line in the same completion. Reject the turn. Retry once. Then handoff.

Parallel tool calls: validate each object against the allowlist. “The batch was mostly fine” is how a forbidden name sneaks through.

One legal example, two evals

Put one legal JSON example in the spec if the format is picky. Put a legal example last if you few-shot. Evals: valid JSON parses; unknown tool fails closed; prose fails closed. That is enough to catch most Tuesday breaks.

Do not few-shot a thought that claims a tool ran. That trains lies.

Live PythonOpen full playgroundpython
Output
Run to execute this in your browser. Nothing is sent to a server.

What printed: step 1 runs get_job, step 2 finishes, OUT is the final dict. Prose raises JSONDecodeError. Unknown tools would raise ValueError. The prompt must teach this shape. The parser must not accept a smile.

This toy calls functions so you can see a loop. The lesson is still the format. How you implement get_job in production is a later track.

Walkthrough: the smile that broke Tuesday

The prompt allowed “JSON or a short plan.” Tuesday’s completion is “I will now Get-Job with id seventeen :)”. The parser has no object. You add regex. Wednesday it is XML. Two dialects are two contracts. The prompt that works asks for one object, shows one legal example last, and refuses prose. Evals: valid JSON; unknown tool fails; prose fails. Thoughts that claim a tool ran without a tool message are graded as lies against the transcript.

Parallel calls: validate each object. “Mostly fine” is how a forbidden name sneaks through.

What goes wrong if you skip this

You regex production. Partial batches refund and fail to log. Thoughts become the API. Classic ReAct blogs leak a second dialect into the live site. The runtime track cannot help if you cannot parse an action.

ReAct is a format constraint. Thought is optional. The action name and arguments are the product. Prefer one JSON object per turn with keys the parser already knows. Show one legal example. Refuse prose. Unknown tools fail closed. Thoughts that say “I called get_job” without a tool message are lies; the transcript is the truth.

If you still like Action: lines from a blog, parse them once and convert to JSON internally. Do not accept two dialects in production. Parallel calls: validate each object against the allowlist. “The batch was mostly fine” is an incident.

Evals for this lesson are small: valid JSON parses; unknown tool fails; smile-prose fails. That catches most Tuesdays.

Common mistakes

MistakeTuesday symptomFix
JSON or a planSmile proseOne object
Two dialectsRegex farmOne dialect
Thought as APILies in the tapeParse tool, not poetry
Unvalidated parallelOne forbidden nameValidate each
Few-shot fake callsHallucinated toolsNever show a fake tool message

Write two golden completions: one legal object the parser accepts, one smile the parser rejects, one unknown tool name the parser rejects. That is the format suite. If those three are not in CI, you will learn the dialect drift from production. The prompt can show one legal example. The suite must show the illegal ones. Format in text, enforcement in code, proof in evals.

How agents use this

One legal example in the spec. Two evals: valid JSON, unknown tool. Parallel calls: validate each object. Thoughts are optional logs. The action name and arguments are the API. If a completion contains two objects, validate both or reject the turn. If it contains a smile, reject. Retry once with “return only the object.” Then handoff. Do not grow a second parser for “almost JSON.” Tuesday’s dialect is not your product. The format is. Keep the parser boring: loads, enum, args object, stop. Anything else is a fail. That boring parser is the product. Fancy recovery is how you accept a smile on Thursday.

Watch out:Thoughts that say “I called get_job” without a tool message are lies. The transcript is the truth.

Check your understanding

What must the parser get right 100% of the time in ReAct?