← All playbooks

Interactive

LLM vs Agent

An LLM is a function. An agent is a loop.

Both take a prompt. An LLM gives you an answer in one shot. An agent can call tools, see the results, think again, and keep going until it decides it's done. That loop — plus tools, state, and a stop condition — is what turns an LLM into an agent.

"I'm flying to Chicago tomorrow. Should I pack a coat?"

LLM one call, no tools

Agent loop + tools

What an agent is made of (beyond the LLM)

  • Tools — functions with schemas the LLM can call to reach outside its own head: search, API calls, file I/O, SQL, shell, another LLM.
  • State — conversation history the LLM sees every turn, so it can build on what it has learned instead of starting fresh.
  • The loop — the runtime that keeps calling the LLM, executes any tool calls, feeds the results back, and repeats.
  • Termination — the agent stops when it says it's done. You set a max-turn safety net in case it won't stop.
  • Guardrails — max turns, tool allowlists, timeouts, sandboxes, approval prompts for risky actions, cost caps.
  • A goal — usually implicit in the prompt ("find X", "fix Y"). Fancier agents plan explicitly and reflect on progress.

The agent loop, in ~30 lines

The loop is the thing. Everything else — tools, prompts, state — is setup.

# 1. TOOLS — functions the LLM is allowed to call
tools = [{"name": "get_weather", "description": ..., "input_schema": {...}}]

# 2. STATE — conversation history grows each turn
messages = [{"role": "user", "content": user_prompt}]

# 3. LOOP — this is what makes it an agent
for turn in range(MAX_TURNS):
    response = client.messages.create(model=..., tools=tools, messages=messages)
    messages.append({"role": "assistant", "content": response.content})

    # 4. TERMINATION — the AGENT decides when it's done
    if response.stop_reason == "end_turn":
        return final_text(response)

    # 5. TOOL USE — run tools, feed results back, loop again
    tool_results = []
    for block in response.content:
        if block.type == "tool_use":
            output = run_tool(block.name, block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": output,
            })
    messages.append({"role": "user", "content": tool_results})
    # ↑ next turn the LLM sees the results and decides what to do next

The full runnable version

↓ agent.py

Same loop as above, with a stub tool and an LLM-only baseline for comparison. Drop in your ANTHROPIC_API_KEY and run it.

pip install anthropic
export ANTHROPIC_API_KEY=sk-...
python agent.py