Modern AIAgents & Tool Use

LLM Agents and Tool Use

Background
  • ReAct (Yao et al. 2022) showed LLMs could interleave reasoning and tool use in an agent loop
  • AutoGPT and BabyAGI (2023) demonstrated autonomous multi-step agents, highlighting both potential and instability
  • Function calling APIs (OpenAI 2023) made tool use reliable enough for production deployments
The Concept

An AI agent is an LLM that can take actions (call tools, browse the web, write/execute code, query databases) and observe the results over multiple steps to accomplish a goal. The agent loop: plan -> act -> observe -> plan continues until the task is complete.

How it Links
  • Connects to classic symbolic AI planning and the STRIPS formalism
  • Multi-agent frameworks (AutoGen, CrewAI) deploy multiple specialized agents with coordination protocols
  • RAG is often one tool available to an agent for knowledge retrieval
Concrete Example
A coding agent given a GitHub issue: reads the issue, checks the relevant code files, writes a fix, runs the tests, sees 2 failures, reads the error messages, adjusts the fix, runs tests again, all failures pass - opens a PR. No human involved after the initial task specification.
Learning outcomes
  • Explain why an LLM agent is the composition of three pieces (planner, tool surface, memory) and what each one buys you.
  • Walk through a ReAct trajectory step by step and write down a maximum-likelihood loss for fine-tuning on Thought / Action / Observation traces.
  • Compare ReAct, Tree of Thoughts, Reflexion, and plan-and-execute on the search type they perform, when each wins, and the per-task token cost.
  • Diagnose the four standard agent failure modes (infinite tool loops, context bloat, missing observation truncation, hallucinated tool calls) and pick the right fix.
  • Read SWE-Bench / AgentBench / WebArena numbers without overclaiming, and explain why "the context window is a poor agent state" still bites in 2024.

TL;DR

TL;DR

An LLM agent is a closed loop where a language model writes its own next move and an outer harness executes it. The recipe that actually works in production is small: chain-of-thought prompting [1] for free-form reasoning, the ReAct Thought / Action / Observation trace template [2] for tool use, structured function calling as the wire format, and a few task-specific augmentations on top (Tree of Thoughts [4] for search-heavy problems, Reflexion [5] for tasks where you can fail and retry, and a multi-agent harness like AutoGen [6] when you need explicit role separation). The rest is engineering: trim observations, cap turns, treat the context window as a poor agent state, and measure on a real benchmark instead of a demo. As of 2024 the best general agents still pass only 3030 to 50%50\% of focused tasks on SWE-Bench Verified and AgentBench, so build accordingly.

Intuition: agents are planners + tool users + memory

Strip away the framework names and an LLM agent is three pieces glued together. The planner is the LLM itself, queried with the current state and asked to emit the next step in some structured format. The tool surface is the set of side-effecting actions the harness exposes (a search API, a Python sandbox, a filesystem read, a browser click). The memory is whatever the harness puts back into the prompt on the next turn: the conversation so far, retrieved documents, summarized scratchpads, persistent vector stores. The loop is prompt -> emit action -> execute -> observe -> repeat, and every interesting design choice is about what gets carried from one turn to the next.

That decomposition matters because each piece fails differently. A planner failure looks like a bad next step on a clean state; the fix is better prompting, more capable model, or fine-tuning on better trajectories. A tool-surface failure looks like a correct plan that the harness cannot execute; the fix is more tools, better schemas, or a sandbox that returns useful errors. A memory failure looks like the agent forgetting the goal twenty turns in, repeating itself, or cycling through the same three actions; the fix is summarization, observation truncation, and a hard turn cap. The frameworks below (LangChain, AutoGen, LangGraph, CrewAI) are all opinionated takes on how to wire these three pieces together. None of them removes the underlying decomposition; pick a framework that surfaces it cleanly so you can debug it.

  1. 2022Chain-of-thought promptingpaper

    Wei et al. show that adding "Let us think step by step" or a few worked examples of reasoning to the prompt unlocks multi-step arithmetic, commonsense, and symbolic tasks. CoT is the planner half of every agent that came after.

  2. 2022ReAct: Reason + Actpaper

    Yao et al. interleave free-form reasoning ("Thought:") with discrete tool calls ("Action:") and ground each step in tool output ("Observation:"). On HotpotQA and ALFWorld, ReAct beats both CoT-only and act-only baselines because reasoning can route and observations can correct.

  3. 2023Toolformer: self-supervised tool-usepaper

    Schick et al. teach a base LM to use a calculator, search engine, calendar, translator, and Q&A by sampling candidate API calls and keeping the ones that reduce next-token perplexity. The model fine-tunes itself on the keepers.

  4. 2023Tree of Thoughtspaper

    Yao et al. lift CoT from a single chain to a search tree: each node is a partial solution, an evaluator (also an LLM call) prunes branches, and BFS or DFS finds the best leaf. ToT wins on Game-of-24, mini-crosswords, and creative writing where deliberate search beats greedy decoding.

  5. 2023Reflexion: verbal reinforcementpaper

    Shinn et al. add a self-critique step after each failed attempt. The model writes a short "I should have ..." note that gets appended to the prompt for the next attempt. Cheap, no gradient updates, and lifts pass rates on coding and decision-making benchmarks.

  6. 2023AutoGen and the multi-agent waveframework

    Wu et al. ship a multi-agent conversation framework. AutoGPT, BabyAGI, MetaGPT, CrewAI, and LangGraph follow within months. Most production agents in 2024 still look like a single ReAct loop with tools; the multi-agent wins are real but specific.

An agent is a closed loop with three failure modes

A useful mental model:

  • Planner. “Given everything I know, what should the next step be?” Failure looks like a bad action on a clean state.
  • Tools. “How do I make that step happen in the world?” Failure looks like a correct plan blocked by a missing capability or a bad schema.
  • Memory. “What do I carry into the next prompt?” Failure looks like forgotten goals, repetition, and cycles.

Every agent paper below tweaks one of these three and leaves the other two alone. CoT and ReAct are planner-side; Toolformer is tool-side; Reflexion is memory-side. Knowing which axis you are touching keeps the design honest.

ReAct: the Reason + Act loop

The single most-copied agent recipe in 2024 is ReAct [2]. It is a prompt template, not a model, and the template is simple enough to reproduce in a few lines.

ReAct trajectories: Thought / Action / Observation

The harness runs a loop. At each turn the prompt contains the system instructions, the user task, the trace so far, and the literal token Thought:. The model completes a free-form thought, then emits Action: <tool_name>[<arg>], then stops. The harness parses the action, executes it, formats the result as Observation: <result>, appends it to the trace, and queries the model again. The terminal action is Action: Finish[<answer>], which exits the loop.

A worked HotpotQA-style example:

Question: Which film came out first, Inception or Interstellar?

Thought: I need the release years of Inception and Interstellar.
Action: Search[Inception movie release year]
Observation: Inception is a 2010 science fiction film directed by Christopher Nolan.

Thought: Inception is 2010. Now I need Interstellar's release year.
Action: Search[Interstellar movie release year]
Observation: Interstellar is a 2014 science fiction film directed by Christopher Nolan.

Thought: 2010 is earlier than 2014, so Inception came out first.
Action: Finish[Inception]

Three structural properties matter:

Reasoning routes. The thought decides which tool to call and with what argument. CoT-only baselines have to guess the answer in one shot; act-only baselines call tools but cannot plan across calls. ReAct ties the two together so the planner can ask “what do I still need” before each action.

Observations correct. A tool that returns “no results” or a wrong fact gets folded into the next thought; the model can revise its plan rather than commit to a hallucinated answer. This is why ReAct beats CoT on tasks with external state (HotpotQA, FEVER, ALFWorld, WebShop) but does not beat CoT on closed-book reasoning, where there is nothing to observe.

The trace is the state. Everything the harness needs is in the appended text. There is no hidden controller; the model is the controller, and the harness is just a shell that parses tokens.

Why tool calls happen at all. A pre-RLHF base LM has no built-in reason to emit Action: when given a question. ReAct works because (1) instruction-tuned LMs follow the template they are given a few-shot examples of, and (2) RLHF-tuned LMs further reward tool-grounded answers over confidently wrong free-form ones. Without either of those, the model will happily ignore the template and answer in prose.

ReAct is load-bearing, not flashy. Most production agents (LangChain agents, OpenAI Assistants, the original AutoGPT inner loop) are ReAct or a thin variant, and most “advanced” agent frameworks are also ReAct underneath with a different parser. If your agent is broken and you do not know why, instrument the trace and read it: the bug will be in a Thought, an Action argument, or an Observation that does not match what was asked for.

A maximum-likelihood loss on ReAct trajectories

When you fine-tune a model on ReAct trajectories, the loss is just next-token cross-entropy on the trace, with one important nuance: you should not include the observation tokens in the loss, because the model does not generate observations. Let a trajectory be the sequence

τ=(x,t1,a1,o1,t2,a2,o2,…,tT,aT)\tau = (x, t_1, a_1, o_1, t_2, a_2, o_2, \dots, t_T, a_T)

where xx is the user task, tit_i is the ii-th thought (tokens), aia_i is the action token sequence, and oio_i is the observation returned by the harness. The loss masks out xx and every oio_i:

L(θ)=−∑τ∈D∑i=1T[log⁡pθ(ti∣prefix<ti)+log⁡pθ(ai∣prefix<ai)],\mathcal{L}(\theta) = - \sum_{\tau \in \mathcal{D}} \sum_{i=1}^{T} \left[ \log p_\theta(t_i \mid \mathrm{prefix}_{<t_i}) + \log p_\theta(a_i \mid \mathrm{prefix}_{<a_i}) \right],

where prefix<u\mathrm{prefix}_{<u} denotes every token in τ\tau that comes before uu in linear order. The mask is the only difference from standard SFT and is the difference between an agent that knows when to act and one that hallucinates observation tokens itself [7].

Toolformer: self-supervised tool-use

Toolformer [3] answers a different question: how do you teach a base LM to use tools without expensive labelled trajectories? The trick is fully self-supervised.

Start with a corpus of plain text and a small set of tools (calculator, Wikipedia search, calendar, translator, question answerer). For every position in the corpus, the base LM is prompted with a few-shot examples and asked to propose an API call that would fit at that position, with its argument. Each proposal is then executed to get the response. For each candidate API-call-and-response, compare the next-token perplexity of the original continuation with the API call inserted versus without. Keep only the calls where the perplexity drop is at least a threshold τ\tau on the actual next tokens.

The result is an automatically labelled dataset of (text, tool-call, response, kept-or-not) tuples; fine-tune the base LM on the kept tuples. The fine-tuned model now emits <API> tool(args) -> result </API> tokens in line, and at inference time the harness intercepts those tokens, executes the call, and replaces the result with the real one before continuing generation.

Toolformer’s contribution is methodological more than practical (the original model is a 6.7B GPT-J, and modern instruction-tuned LLMs already use tools competently). The transferable idea is scoring tool calls by how much they reduce perplexity on the actual next tokens, which gives a clean self-supervised signal whenever you can re-run the LM on the post-tool-call context.

Function calling and tool-use APIs

Production agents do not parse Action: Search[query] strings. They use structured function calling: the model emits a JSON object with a tool name and arguments, validated against a JSON Schema, and the harness dispatches accordingly.

Three flavours dominate in 2024.

OpenAI function calling. Tools are declared with a JSON Schema (name, description, parameters). The model emits a tool_calls array on its message; the harness runs each tool, appends the results as messages with role: "tool", and queries the model again. The schema is enforced by the model server, so malformed JSON is rare in practice.

Anthropic tool use. Same shape (declare tools with JSON Schema, model emits a tool_use content block, harness appends tool_result blocks). Anthropic also exposes a separate computer use modality where the tool surface is the screen itself: actions like screenshot, mouse_move, mouse_click, key, type. The model receives observations as image tokens.

Google function calling on Gemini. Same idea, slightly different wire format; supports parallel function calls in a single turn.

The portable lesson is that the schema is the contract. A tool with a vague description and loose argument types invites the model to invent calls; a tool with a tight schema and a clear description gets called correctly. Write the schema like API documentation, not like an internal helper.

Tool APIWire formatSchema enforcementMultimodal observationsWhere it shines
OpenAI function callingJSON tool_calls + tool messagesServer-side JSON SchemaText only (vision via separate input)Default for most LangChain / Assistants pipelines
Anthropic tool_usetool_use / tool_result content blocksServer-side JSON SchemaNative image observations (computer use)Browser and desktop automation; Claude Code
Gemini function callingJSON function_call + function_responseServer-side JSON Schema; parallel callsNative multimodal (image, audio, video)Long-context multimodal pipelines
Custom ReAct loopPlain-text Thought / Action / ObservationRegex parser in your harnessWhatever you stringifyOpen models without function-calling fine-tunes; full control of the prompt

ReAct is a greedy left-to-right walk. For tasks where the next correct step is not obvious, search beats greedy. Tree of Thoughts [4] formalizes that idea.

ToT: BFS or DFS over a tree of partial solutions

A ToT run has four ingredients:

Thought decomposition. Break the problem into a sequence of steps where each step is short and human-checkable. For Game-of-24 a thought is a single arithmetic operation; for creative writing it is a paragraph plan; for mini-crosswords it is a single word fill.

Thought generator. Given a partial trace, an LLM call proposes the next kk candidate thoughts. This is the branching factor. In the original paper kk is typically 11 to 55, depending on the task.

State evaluator. Another LLM call scores each candidate state on its progress toward the goal. The evaluator returns either a value (small enumeration like “sure / likely / impossible”) or a vote among the candidates. Either way, it is the heuristic that lets search prune.

Search algorithm. BFS keeps the top-bb states at each depth (beam search over thoughts); DFS explores one branch to completion, backtracks on a low evaluator score. BFS is the default for finite-depth tasks (Game-of-24 is at most 3 steps); DFS is the default for variable-depth tasks (crosswords, where you may fill a few words and then have to backtrack).

The end-to-end ToT cost is roughly

tokensToT≈depth×k×(generator tokens+evaluator tokens),\text{tokens}_\text{ToT} \approx \text{depth} \times k \times (\text{generator tokens} + \text{evaluator tokens}),

so a depth-3 search with k=5k = 5 and a small evaluator runs 1515 to 2020 times the tokens of a single CoT chain. The cost is paid back on tasks where CoT is well below ceiling: Game-of-24 jumps from 4%4\% on CoT to 74%74\% on ToT in the original paper. On tasks that CoT already solves, ToT is wasted compute.

ToT generalizes naturally to graph search if multiple branches converge on the same partial solution; in practice it is rare to need that, and a tree is enough.

The lesson is sharper than “search beats greedy”. CoT is implicitly a single path drawn by the model’s sampling distribution; ToT lets you draw many paths and pick the best. If your task has a clear evaluator (does this code compile, does this proof type-check, is this state closer to the goal) ToT’s evaluator step has real signal and search pays off. If your evaluator is itself a vague LLM judge, ToT mostly adds cost.

Reflexion: verbal reinforcement

Reflexion [5] is the cheapest agent-improvement trick in the literature: no gradients, no reward model, no extra fine-tune. After each failed attempt, the model writes a short verbal reflection (“I tried XX, that failed because YY, next time I should ZZ”), the reflection gets appended to the agent’s long-term memory, and the next attempt sees both the original task and the accumulated reflections.

Three observations:

Verbal feedback works because LLMs are good in-context learners. A reflection is just more context. Telling the model “last time you forgot to check the date, do not forget this turn” is functionally similar to a reinforcement-learning update, except the update lives in the prompt rather than in the weights.

It needs a real failure signal. Reflexion needs an external “you failed” signal: a unit test, a benchmark grader, an environment terminal reward. Without that signal you cannot tell which attempt to reflect on, and the reflection becomes performative.

It interacts cleanly with ReAct. A common production setup is ReAct as the inner loop, Reflexion as the outer loop: each ReAct trajectory either finishes or hits a turn cap, the outer loop checks whether the answer passes, and on failure produces a one-paragraph reflection that goes into the system prompt for the next attempt. Reflexion lifts HumanEval pass-rates by several points and AlfWorld success rates by tens of percentage points [5].

The pattern is general: store the lesson, not the weights. For tasks with cheap external graders, this is one of the highest-leverage agent tricks.

Multi-agent systems

Once you have a single agent that works, the natural next step is multiple agents talking to each other. Three production frameworks dominate.

AutoGen [6]. Agents are first-class objects with names, system prompts, and tools. They send messages to each other through a conversation manager. The headline patterns are user proxy + assistant (the user proxy executes code that the assistant writes) and group chat (an explicit speaker-selection rule among NN agents). AutoGen is closest to “ReAct generalized to multi-party conversation”; the bookkeeping is messages and turn order rather than Thought / Action / Observation.

CrewAI. Agents have roles and goals, and tasks are explicitly delegated by a manager agent. The mental model is a small fixed org chart: a researcher, a writer, a critic. Useful when you can sketch the org chart up front; less useful when the role boundaries are fluid.

LangGraph. Drops the conversational metaphor entirely and exposes the agent as a directed graph of nodes (LLM calls, tool calls, conditional branches). This is the most explicit framework, and the easiest one in which to build agents that are not loops but actual state machines. LangGraph is also the framework most often used for production agents in 2024 because it makes the control flow visible.

The honest summary is that multi-agent wins are real but specific. They show up cleanly when the task has a clear separation of responsibilities (research vs writing vs critique), where one agent has a system prompt the other should not see, or where you want explicit role-level safety boundaries (a separate agent that has the database write tool and refuses on unsafe inputs). For a single end-to-end task with no role separation, a single ReAct agent with the right tools usually beats a multi-agent system on both quality and latency.

Plan-and-execute architectures

Plan-and-execute is the third common control structure. It splits the loop into two phases.

Phase 1: plan. A planner LLM produces an explicit step-by-step plan (often as a numbered list or a JSON DAG) for the whole task at once.

Phase 2: execute. A separate executor LLM (or the same LLM with a different prompt) walks the plan one step at a time, calling tools, and appending results.

Compared to ReAct, plan-and-execute has two strengths and one weakness. The strengths: the plan is reviewable up front (a human or a separate critic can check it before any tool is called), and the executor’s per-step prompt is shorter (it only needs the current step plus relevant context, not the whole trace). The weakness: a plan made up front cannot use information that only appears mid-execution, so plan-and-execute is brittle on tasks where early observations should change the plan. Hybrid forms (“plan, execute one step, optionally re-plan”) restore the adaptivity at the cost of more LLM calls and a more complex harness.

PatternSearch typeWhen it winsPer-task costTypical failure mode
ReActGreedy left-to-right, one traceMost general tool-use tasks; HotpotQA, ALFWorld, WebShop, default agent loops1 trace x N turns; cheapCycles when the model hallucinates and feeds itself bad observations
Tree of ThoughtsBFS or DFS over partial-solution treeSearch-heavy tasks with a clean evaluator (Game-of-24, mini-crosswords, planning)15-20x ReAct on the same problemWasted compute when the evaluator has no real signal
ReflexionOuter-loop self-critique on top of any inner loopTasks with cheap external graders (unit tests, benchmark scorers)1.5-3x ReAct (one extra critique per attempt)No-op without a real failure signal; reflections become performative
Plan-and-executeUp-front plan, then sequential executionTasks where the plan is reviewable up front and stable across executionAbout 1x ReAct for fixed plans; more if you re-planBrittle when early observations should change the plan

Memory: short-term, long-term, and the limits of the context window

The context window is the agent’s short-term memory. Everything in the current prompt (system, task, trace so far, retrieved chunks) is “remembered”; everything outside is forgotten unless the harness re-injects it. Long-context models (Claude 200200k, Gemini 11M, GPT-4 turbo 128128k) push that horizon out, but they do not change the structural problem: bigger context is more expensive per token, slower to attend over, and increasingly noisy as you stuff it.

Production agents handle memory in three layers.

Short-term: the live trace. The current ReAct trace, with observations truncated to a per-tool budget. A naive harness lets a single observation balloon to 2020k tokens (a full HTML page, a verbose stack trace, a giant JSON dump) and then the next prompt has no room for further reasoning. The fix is a per-tool truncation policy: top-kk snippets, head-and-tail truncation, or a quick summarization call.

Medium-term: summarized scratchpad. Once the trace passes a threshold, the harness summarizes the older half (“so far the agent has tried AA, BB, CC and learned that XX does not work because YY”) and replaces it with the summary. The summarization step is one extra LLM call per trim and trades fidelity for room.

Long-term: vector store. Cross-task memories (a coding agent remembering past projects, a research agent remembering past queries) live in a vector store and are retrieved like any other RAG corpus. The retrieved snippets are appended to the current prompt as part of the system message. This is where agent memory blurs into RAG; the difference is mostly whether the corpus is the user’s documents (RAG) or the agent’s past traces (long-term agent memory).

The context window is a poor agent state

A common mistake is to treat the context window as the agent’s full state. It is not: it is a recency-biased view of the trace, with the model attending unevenly across positions and a cost that grows roughly linearly in length. Two practical consequences:

  • Recency bias. The model attends most to the system message and the last few turns; mid-context information is silently underweighted (“lost in the middle”). Putting the goal at the very top and the latest observation at the very bottom is a real intervention, not a cosmetic one.
  • Cost grows fast. Every extra observation token gets re-paid on every subsequent turn, because the whole trace is re-tokenized in each prompt. A 5050-turn trace with 55k tokens of observation per turn is 250250k tokens at the end, regardless of context-window size.

Trim observations, summarize the older trace, and put the goal somewhere the model will keep attending to it. None of those are optional at scale.

Computer use, browser use, and code-execution sandboxes

Two parallel evolutions extended the agent surface in 2024. Both are about what the agent can do in the world.

Computer use. Anthropic’s Claude computer use and OpenAI’s Operator expose the desktop or browser as a tool. The action space includes screenshot, mouse_move, mouse_click, key, and type; the observation is an image of the screen. The model has to read pixels to know where to click, which means it has to be a strong vision model. Claude 3.5 Sonnet (computer use) and the later Claude 3.7 / 4 generations are the practical state of the art; pass rates on OSWorld and similar benchmarks are still in the 2020 to 40%40\% range as of late 2024.

Browser use. A narrower version of computer use: the harness exposes navigate, click(selector), fill(selector, text), extract, screenshot. The DOM is a much cleaner state than the screen, so the agent can run on cheaper text-only models. Browser-Use, Stagehand, and Playwright-based wrappers are the common implementations; AgentBench and WebArena are the evaluation benchmarks. Pass rates here are higher than on full computer use (roughly 3030 to 60%60\% depending on task) because the abstraction is cleaner.

Code execution sandboxes. E2B and Riza (and the older Code Interpreter / Advanced Data Analysis surface) wrap a Python or Node sandbox as a tool. The harness ships a python(code) action; the sandbox runs the code, captures stdout / stderr, and returns the result. This is the highest-signal tool for an agent because the failure mode is unambiguous (the code either works or raises) and the action space (write a Python program) is enormous. Code-execution sandboxes are the closest production agents get to real generality; most non-trivial agent benchmarks (GAIA, SWE-Bench) require some form of code execution.

Critical evaluation: agents are unreliable at scale

It is easy to demo an agent. It is hard to ship one. As of late 2024 the public agent benchmarks land in this range:

  • SWE-Bench Verified (real GitHub issues fixed end-to-end). Best public agents land around 4040 to 55%55\% pass rates on the verified subset; below 20%20\% a year earlier. Progress is real but the long tail is hard.
  • AgentBench (multi-environment generalist benchmark across OS, DB, web, code). Best models score in the 3030 to 5050 range out of 100100. Specialist humans clear 90%90\% on most subsets.
  • GAIA (general AI assistant tasks). Best public agents pass roughly 4040 to 50%50\% of level-1 tasks and far less on harder tiers.
  • WebArena (real web browsing tasks). Best agents pass roughly 3030 to 45%45\%.

The numbers say two things at once: agents are good enough to be deployed in narrow domains (Devin and Cursor agents on code, Claude Code on shell tasks, customer-service agents on closed-domain Q&A), and they are not good enough to be deployed as general assistants without heavy human-in-the-loop guardrails. Plan accordingly: a 40%40\% pass-rate is the bar, not the floor.

A custom ReAct loop in fifty lines

A useful reality check is that the entire ReAct pattern fits in a small file. Here is a self-contained loop in pure Python plus the OpenAI client, alongside the equivalent in three production frameworks.

python
import re
from openai import OpenAI

client = OpenAI()

def search(q):
  return f"(stub) results for: {q}"

def calc(expr):
  return str(eval(expr, {"__builtins__": {}}, {}))

TOOLS = {"Search": search, "Calc": calc}

SYSTEM = """You are a ReAct agent. On each turn emit:
Thought: <one sentence reasoning>
Action: <ToolName>[<arg>]
or, when you have the answer:
Action: Finish[<answer>]
Stop after the Action line."""

def run(task, max_turns=8):
  trace = f"Question: {task}\n"
  for _ in range(max_turns):
      msg = [
          {"role": "system", "content": SYSTEM},
          {"role": "user", "content": trace + "Thought:"},
      ]
      out = client.chat.completions.create(
          model="gpt-4o-mini",
          messages=msg,
          stop=["Observation:"],
          temperature=0,
      ).choices[0].message.content
      trace += "Thought:" + out
      m = re.search(r"Action:\s*(\w+)\[(.*?)\]", out, re.S)
      if not m:
          return "(parse error) " + out
      tool, arg = m.group(1), m.group(2)
      if tool == "Finish":
          return arg
      if tool not in TOOLS:
          obs = f"unknown tool: {tool}"
      else:
          obs = TOOLS[tool](arg)
      trace += f"\nObservation: {obs}\n"
  return "(turn cap reached)"

print(run("What is the population of Tokyo, in millions, divided by 2?"))
Fifty lines, no framework. The harness is a regex parser plus a dict of tools. Notable: the stop sequence forces the model to halt after Action; the temperature is 0 to keep the trace stable; the turn cap is a hard floor against infinite loops.

The custom-loop and framework versions land in roughly the same place semantically. The framework adds tracing, retries, and a more battle-tested parser; the custom loop is fifty lines you can read end to end, which is sometimes the thing you need.

Real-world agent stacks

The agent landscape that actually shipped in 2023 to 2024:

  • Devin (Cognition). The first widely-publicised “AI software engineer”. A long-running agent with a planner, a code-execution sandbox, a browser, and persistent state across days. Numbers on SWE-Bench were the first to break 10%10\% on the full suite.
  • Cursor agents and Claude Code. Editor-integrated coding agents. Smaller scope per turn (refactor this file, fix this test, run this command) and consequently much higher reliability than open-ended agents. The most common production agent pattern in 2024.
  • OpenAI Operator and Anthropic computer use. General computer-use agents on the desktop or browser. Real, but in 2024 still gated to focused tasks behind heavy human oversight.
  • AutoGPT, BabyAGI, MetaGPT. The 2023 wave that demonstrated the demos and exposed the problems (cost, infinite loops, unreliable tool use). Influential as research artifacts; superseded as production stacks.

Pitfalls

Infinite tool loops

The most common runtime failure: an agent calls the same tool with the same argument turn after turn, gets the same observation, draws the same thought, and never terminates. The mechanism is greedy decoding plus a deterministic tool: the model’s most likely next action given the current state has not changed, because the state has not changed.

Fix. Hard turn caps (eight to twelve turns is reasonable for most tasks). Detect repeats by hashing the last kk (action, argument) pairs and breaking on match. Add a small temperature (0.20.2 to 0.50.5) to the planner so successive draws differ. Surface the loop to the operator with a “stuck on action XX” signal.

Context bloat

Each turn re-tokenizes the entire trace. A trace that grows linearly in turns runs at quadratic cost overall, because every turn pays for every previous turn’s tokens again. By turn 3030 even a careful agent can be paying for 100100k tokens of history per call, which is both slow and expensive.

Fix. Summarize the older half of the trace once it crosses a threshold (e.g. 44k tokens). Drop or compress older observations more aggressively than older thoughts, because thoughts are usually short and observations are usually long. Track total tokens per turn as a metric; alert when it grows past a budget.

Missing observation truncation

A single observation can wreck an agent: a 5050k-token HTML dump, a giant Python traceback, a verbose JSON response. The model attends unevenly across that mass, the rest of the prompt gets pushed to the noisy edges, and the next thought is incoherent.

Fix. Truncate every tool’s observation to a per-tool budget (e.g. 11k tokens for search, 22k tokens for code execution, 44k tokens for retrieved documents). Use head-and-tail truncation rather than head-only when the relevant signal could be at either end (stack traces, paginated lists). Optionally pass the full observation through a one-shot summarizer before appending.

Hallucinated tool calls

The model emits Action: search_database[...] for a tool that does not exist, or invents arguments not in the schema. With plain-text parsers this slips through silently and the harness fails to dispatch; with structured function calling the schema validation catches the malformed argument but the model can still call the wrong tool.

Fix. With JSON Schema, set additionalProperties: false and tight type constraints. With plain-text parsers, validate the tool name against the registered set and surface “unknown tool: XX. Available: YY.” back to the model as the observation. Always include the tool registry in the system prompt; do not rely on the model remembering it across long traces.

Pop quiz

Check yourself
Score: 0 / 5(0 answered)
  1. 1. What does the "Observation" line in a ReAct trace correspond to?
  2. 2. Toolformer trains a base LM to use tools by:
  3. 3. When does Tree of Thoughts (ToT) reliably beat plain chain-of-thought?
  4. 4. Reflexion improves agent performance primarily by:
  5. 5. In a custom ReAct loop, the most common reason to set a hard turn cap is:

Interview Q&A

Write down a maximum-likelihood loss for fine-tuning an LLM on ReAct trajectories, and explain what gets masked and why.research

Setup. A ReAct trajectory is a sequence

τ=(x,t1,a1,o1,t2,a2,o2,…,tT,aT)\tau = (x, t_1, a_1, o_1, t_2, a_2, o_2, \dots, t_T, a_T)

where xx is the user task, tit_i is a thought (free-form tokens), aia_i is an action (tool name and argument tokens), and oio_i is an observation returned by the harness. The goal is to train a single autoregressive model pθp_\theta that produces the next thought and action given the prefix.

Loss. Standard next-token cross-entropy on the trace, with the user task xx and every observation oio_i masked out of the loss:

L(θ)=−∑τ∈D∑i=1T[log⁡pθ(ti∣prefix<ti)+log⁡pθ(ai∣prefix<ai)].\mathcal{L}(\theta) = - \sum_{\tau \in \mathcal{D}} \sum_{i=1}^{T} \left[ \log p_\theta(t_i \mid \mathrm{prefix}_{<t_i}) + \log p_\theta(a_i \mid \mathrm{prefix}_{<a_i}) \right].

The mask is the substantive design choice. Three reasons it has to be there:

  1. The model never produces observations. oio_i is whatever the harness wrote back from a tool. If the loss includes oio_i, the model is trained to predict the tool’s output, which it cannot do reliably and which encourages it to hallucinate observations at inference time when no harness is present.
  2. The model never produces xx. xx is the user’s input. Including it in the loss is a small SFT-style mistake: the model wastes capacity on copying user prompts.
  3. The mask preserves the agent abstraction. The model’s job is to write thoughts and actions. Everything else is environment.

Implementation. In PyTorch the trace is concatenated into a single token sequence; a per-token loss_mask is 1 on thought and action tokens and 0 on user-task and observation tokens. The loss is a standard cross-entropy weighted by the mask:

L(θ)=−∑jmjlog⁡pθ(zj∣z<j),\mathcal{L}(\theta) = - \sum_{j} m_j \log p_\theta(z_j \mid z_{<j}),

where zz is the concatenated trace and mj∈{0,1}m_j \in \{0, 1\} is the mask. This is identical in shape to InstructGPT-style SFT, just with a richer mask.

Caveat. For high-quality trajectories you also want to filter on outcome: if the trajectory failed, fine-tuning on it teaches the model to fail. This is where Reflexion or rejection-sampling on outcome lets you keep only the successful traces, and then SFT on the filtered set is the standard recipe [7][2].

Why does chain-of-thought reasoning emerge with scale? What is the empirical signature, and what is the leading hypothesis?research

Empirical signature. Wei et al. [1] observe that CoT prompting (a few-shot prompt with explicit step-by-step reasoning) produces near-zero benefit on small models and a sharp benefit on large models. On GSM8K, models below roughly 102210^{22} training FLOPs do not benefit from CoT prompting, and may even be hurt by it. Above that threshold, CoT gives a step-function jump on multi-step arithmetic, commonsense, and symbolic tasks. The pattern is consistent across PaLM, GPT-3, and LaMDA scales; the threshold is loose, but the shape (flat then jump, not gradually improving) is robust.

Leading hypothesis: implicit composition. Multi-step reasoning requires the model to chain partial conclusions across many tokens. At small scale the model has not learned the per-step skills reliably, so a chain of length kk has compounding error: probability of correct chain falls roughly as pkp^k with pp the per-step success rate. At large scale the per-step success rate is high enough that the chain holds, and CoT lets the model externalize the chain rather than try to do all of it in a single forward pass. The compute-budget argument underlines this: producing kk extra tokens of reasoning is O(k)O(k) extra compute at inference, but it lets the model amortize the reasoning across the longer trace rather than try to land the answer in one Transformer pass.

Alternative hypothesis: data distribution. A large fraction of pre-training data on the public web includes step-by-step explanations of math, reasoning, and code. As the model scales it more reliably reproduces the step-by-step style when prompted to. This is a “the model already saw worked examples” account rather than a “the model invents new chains” account. The two hypotheses are not mutually exclusive; both contribute.

Practical implication. Do not expect CoT to lift small models. If you must use a small model, fine-tune it on CoT traces (this lowers the scale threshold by a meaningful factor). If you have access to a large model, CoT is essentially a free win on reasoning-heavy tasks, at the cost of more output tokens.

Limits. “Emergence” claims have been contested: with smoother metrics (token-level cross-entropy rather than exact-match accuracy), the jump can look more gradual. The behavioural threshold on accuracy-style metrics is still real, but the underlying capability curve is closer to monotone improvement than to a true phase transition [1].

Walk through the cost-vs-quality trade-off across ReAct, ToT, Reflexion, and plan-and-execute. When do you reach for each?onsite

Anchor: ReAct as the baseline. A typical ReAct task runs one trace of 55 to 1010 turns, with maybe 500500 tokens per turn including observation. That is roughly 55k tokens of generated output and 2525k tokens of total LLM input across the full trace (because each turn re-tokenizes the trace so far). Call this 1×1\times.

Tree of Thoughts. Branching factor kk and depth dd give roughly kdk^d leaves; with BFS keeping a beam bb at each depth, the cost is closer to b⋅k⋅db \cdot k \cdot d generator calls plus the evaluator calls. Empirically ToT lands at 1515 to 20×20\times ReAct on the same problem, mostly from the per-node evaluator. You reach for ToT when (1) the task has a clear evaluator signal (game state, code compiles, proof type-checks) and (2) CoT or ReAct alone is well below ceiling. You do not reach for ToT on tasks where greedy is already at ceiling, or where the evaluator is just another vague LLM judge.

Reflexion. One extra critique per attempt; if you allow NN attempts, the cost is 1.5×1.5\times to 3×3\times ReAct, depending on how long the reflections are and how you bound retries. You reach for Reflexion when there is a cheap external grader (unit test, benchmark scorer, environment terminal reward). Without that signal the reflections are performative and you just spend money.

Plan-and-execute. The plan is one extra LLM call up front, then sequential execution that is roughly the same cost as a ReAct trace of the same length. Net cost is similar to ReAct, sometimes a bit cheaper (executor prompts are smaller because you do not carry the full trace). You reach for plan-and-execute when (1) the plan is reviewable up front by a human or a critic, (2) the plan is stable across execution (early observations do not change later steps), and (3) you want explicit auditability (“here is the plan, here is what was executed”). You do not reach for plan-and-execute when early observations should change the plan; in that regime, hybrid “plan, execute, optionally re-plan” is closer to ReAct anyway.

Decision sketch.

  • Default to ReAct.
  • Switch to ToT only when the evaluator is real and CoT is below ceiling.
  • Stack Reflexion on top whenever you have a grader and tolerate retries.
  • Use plan-and-execute when auditability or up-front review is the constraint.
  • For safety and tooling separation, use multi-agent (AutoGen, CrewAI, LangGraph). Otherwise the multi-agent overhead rarely pays for itself.
When do agents beat RAG, and when does RAG beat agents?research

Two clean cases first.

RAG wins when the answer is in a corpus. If the user’s question is “what does our refund policy say about returns after 30 days?” and the answer is one paragraph in a doc store, RAG is the right hammer. One retrieval call, one generator call, attribution is clean, latency is low, and you do not pay for any extra LLM turns. Adding an agent on top buys nothing and costs a lot; the worst outcome is the agent looping over the same retriever calls and degrading attribution.

Agents win when the task is multi-step and requires side effects. If the user’s task is “open this customer’s ticket, look up their order in the database, refund the failed transaction, and send a follow-up email”, no amount of retrieval will solve it. The task needs sequential tool calls, conditional branching, and state mutation. ReAct or plan-and-execute over a structured tool surface is the right shape. RAG can be part of the agent (one of the tools is “search the docs”), but RAG alone cannot do the task.

The interesting middle. Many real tasks sit between these poles. Two heuristics for picking:

  1. Is the answer already in your corpus, or do you need to compute or fetch new state? If the former, start with RAG and add agent loops only if a single-shot generator fails. If the latter, start with an agent and use RAG as one tool among several.
  2. How many tool calls per task? Zero or one: RAG. Three or more, with state changing across calls: agent. The middle (two or three calls, mostly read-only) can go either way; RAG with multi-hop retrieval (Self-RAG, FiD-style fusion) often wins on cost.

A second-order point. Agents and RAG are not exclusive. The strongest production patterns in 2024 use RAG inside an agent: one of the agent’s tools is a retriever, the rest are side-effecting actions. This composes the two cleanly: retrieval finds knowledge, the agent loop applies it. The framing “agents vs RAG” is a 2023 framing; the 2024 framing is “RAG is a tool, the agent decides when to use it”.

Failure-mode dual. RAG fails when the retriever cannot find the answer (the chunk does not exist, or chunk-boundary loss splits the answer in half). Agents fail when the planner cannot route, the tools are wrong, or the trace blows the context window. The mistakes are different; if your system fails, identify which one is degrading and fix that piece, rather than rebuilding the whole stack [2].

Build it yourself

A weekend-scale project that turns this page into running code:

  1. Write a custom ReAct loop in 5050 lines of Python and the OpenAI client (the snippet above is a starting point). Wire up two tools: a Wikipedia-style search stub and a Calc tool. Verify it answers 55 HotpotQA-style questions without hallucinating tool calls.
  2. Add a hard turn cap and a repeat-action detector. Force a failure case (a deliberately bad tool argument) and confirm the loop terminates cleanly.
  3. Layer Reflexion on top: on each failure, ask the model for a one-paragraph “next time I should …” reflection, prepend it to the system prompt, retry up to 33 times. Measure the lift on a small held-out set.
  4. Stack a tiny Tree of Thoughts on top of CoT for Game-of-24 with branching factor k=5k = 5, depth 33, and an LLM evaluator. Compare pass rate against single-trace CoT at matched compute.
  5. Stretch goal: re-implement the ReAct loop using OpenAI function calling (or Anthropic tool_use). Confirm the externally observable behaviour is identical, then read the framework’s source and find the three places where it disagrees with your 5050-line version.

References

  1. Wei, J., Wang, X., Schuurmans, D., Bosma, M., Ichter, B., Xia, F., Chi, E., Le, Q., and Zhou, D. (2022). Chain-of-Thought Prompting Elicits Reasoning in Large Language Models. NeurIPS 2022 (arXiv preprint). arXiv:2201.11903
  2. Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K., and Cao, Y. (2022). ReAct: Synergizing Reasoning and Acting in Language Models. ICLR 2023 (arXiv preprint 2022). arXiv:2210.03629
  3. Schick, T., Dwivedi-Yu, J., Dessi, R., Raileanu, R., Lomeli, M., Zettlemoyer, L., Cancedda, N., and Scialom, T. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. NeurIPS 2023 (arXiv preprint). arXiv:2302.04761
  4. Yao, S., Yu, D., Zhao, J., Shafran, I., Griffiths, T. L., Cao, Y., and Narasimhan, K. (2023). Tree of Thoughts: Deliberate Problem Solving with Large Language Models. NeurIPS 2023 (arXiv preprint). arXiv:2305.10601
  5. Shinn, N., Cassano, F., Berman, E., Gopinath, A., Narasimhan, K., and Yao, S. (2023). Reflexion: Language Agents with Verbal Reinforcement Learning. NeurIPS 2023 (arXiv preprint). arXiv:2303.11366
  6. Wu, Q., Bansal, G., Zhang, J., Wu, Y., Li, B., Zhu, E., Jiang, L., Zhang, X., Zhang, S., Liu, J., Awadallah, A. H., White, R. W., Burger, D., and Wang, C. (2023). AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation. arXiv preprint. arXiv:2308.08155
  7. Goodfellow, I., Bengio, Y., and Courville, A. (2016). Deep Learning. MIT Press. https://www.deeplearningbook.org