Day 21 · ~13m

Building an AI Agent

Implement multi-step reasoning with a tool loop and state management.

🧑‍💻

Yesterday we dispatched single tool calls. But what if the AI needs multiple tools to answer one question?

👩‍🏫

That's an agent — an AI that runs a loop: think, act, observe, repeat. Instead of one tool call, it chains multiple steps until it has enough information to answer.

Here's the core loop:

def agent_loop(messages, tools, registry, max_steps=5):
    for step in range(max_steps):
        # 1. Think — ask the LLM what to do
        response = call_llm(messages, tools)
        
        # 2. Check — is it a final answer or a tool call?
        if response.get("tool_call"):
            # 3. Act — execute the tool
            tool_call = response["tool_call"]
            result = dispatch(tool_call["name"], tool_call["args"], registry)
            
            # 4. Observe — add result to conversation
            messages.append({"role": "tool", "content": str(result)})
        else:
            # Final answer
            return response["content"]
    
    return "Max steps reached"

Each iteration: the LLM decides whether to call a tool or give a final answer. If it calls a tool, the result gets added to the conversation and the loop continues.

🧑‍💻

What does the state look like during the loop?

👩‍🏫

The messages list grows with each step:

# Step 0: User asks
[{"role": "user", "content": "What's 15% tip on a $48 meal in Tokyo?"}]

# Step 1: LLM calls calculate
# → tool_call: calculate("48 * 0.15")
# → result: 7.2
[..., {"role": "tool", "content": "7.2"}]

# Step 2: LLM calls get_weather (for context)
# → tool_call: get_weather("Tokyo")
# → result: {"temp": 22}
[..., {"role": "tool", "content": "{\"temp\": 22}"}]

# Step 3: LLM gives final answer
# → "A 15% tip on $48 is $7.20. Enjoy Tokyo at 22°C!"

The conversation is the agent's memory. Each tool result gives it more context for the next decision.

🧑‍💻

How do I prevent infinite loops?

👩‍🏫

The max_steps parameter is your safety net. Most questions resolve in 1-3 steps. If the agent hits the limit, something is wrong — return a graceful error.

You can also add cost tracking:

def agent_loop(messages, tools, registry, max_steps=5, max_tokens=1000):
    total_tokens = 0
    for step in range(max_steps):
        response = call_llm(messages, tools)
        total_tokens += response.get("usage", {}).get("total_tokens", 0)
        if total_tokens > max_tokens:
            return {"answer": "Token budget exceeded", "tokens_used": total_tokens}
        # ... rest of loop
🧑‍💻

Is this basically how ChatGPT plugins and Claude's tool use work?

👩‍🏫

Exactly the same pattern. The models got better at deciding when to use tools and interpreting results, but the loop is identical. Think → act → observe → repeat. Your code controls the tools, the safety limits, and the conversation flow. The LLM provides the reasoning.

Practice your skills

Sign up to write and run code in this lesson.

Already have an account? Sign in