4.1.2. Reasoning Patterns: ReAct and Chain-of-Thought
💡 First Principle: The ReAct (Reasoning + Acting) pattern transforms an FM from a single-turn responder into a multi-step problem solver by interleaving thought (explicit reasoning about what to do next) with action (calling a tool) and observation (incorporating the tool's result before the next thought). This loop continues until the model determines it has enough information to answer.
The ReAct loop in detail:
Thought: The user wants the Q3 revenue compared to budget. I need to retrieve the actual
revenue figure and the budget figure separately, then compute the variance.
Action: query_financial_db(query="Q3 actual revenue", fiscal_year=2024)
Observation: Q3 actual revenue = $4.2M
Thought: I have the actual. Now I need the budget figure.
Action: query_financial_db(query="Q3 budgeted revenue", fiscal_year=2024)
Observation: Q3 budgeted revenue = $3.8M
Thought: I have both figures. Variance = $4.2M - $3.8M = +$0.4M (+10.5%).
I can now answer.
Final Answer: Q3 actual revenue of $4.2M exceeded the $3.8M budget by $400K (+10.5%).
Implementing ReAct with Step Functions (when you need explicit orchestration control):
{
"Comment": "ReAct reasoning loop",
"StartAt": "Think",
"States": {
"Think": {
"Type": "Task",
"Resource": "arn:aws:lambda:::function:invoke-bedrock-reasoning",
"Next": "Route"
},
"Route": {
"Type": "Choice",
"Choices": [
{"Variable": "$.next_action", "StringEquals": "tool_call", "Next": "Act"},
{"Variable": "$.next_action", "StringEquals": "final_answer", "Next": "Respond"}
]
},
"Act": {
"Type": "Task",
"Resource": "arn:aws:lambda:::function:execute-tool",
"Next": "Think"
},
"Respond": {"Type": "Succeed"}
}
}
Controlled orchestration: the FM only proposes the next action; your application validates the proposal, decides whether and when to run it, and feeds the result back for the next step. The Converse API's tool use (the model returns a toolUse request, your code runs the tool and sends back a toolResult) and Bedrock Agents' return of control both work this way. A state machine that simply branches on the action the FM chose, like the loop above, still lets the FM decide which tool runs; the control comes from the validation step. Use it when you need full control over which tools run and in what order; a managed agent plans and executes autonomously.
Stopping conditions — critical for production agents: Without stopping conditions, a misbehaving agent can loop indefinitely, calling tools repeatedly and accumulating costs. Always implement:
- Maximum iteration limit: Hard cap on reasoning steps (e.g., max 10 iterations)
- Timeout: Lambda timeout propagates through; Step Functions state machine timeout
- IAM boundaries: The Lambda execution role limits what the agent can touch — a tool that can only read DynamoDB cannot accidentally delete records even if the agent tries
- Circuit breaker: Detect repeated identical tool calls (infinite loop pattern) and break
⚠️ Exam Trap: A managed agent service runs the reasoning loop for you, but that is no reason to skip loop controls. When you build the loop yourself (a Step Functions state machine or a code-first framework), the stopping conditions are yours to implement: without an iteration cap, a misbehaving agent keeps looping and keeps paying for FM invocations.
Reflection Question: Your Bedrock Agent-powered travel booking system is observed making 40+ tool calls for a simple "book me a flight to NYC" request before timing out. What are the three most likely causes of excessive reasoning steps, and how would you diagnose each?