Skip to content
howdoaiagentswork.com

How to Build an AI Agent with Claude

how to build an ai agent with claude

This Claude tutorial builds a small research agent in Python. Claude decides whether to call a weather-style lookup tool, reads the result, and then answers the user. The same Anthropic API pattern works for search, databases, ticket systems, and internal business tools.

Learn how the reason-act-observe loop works →

What You Will Build

The program has a goal, a Claude model, one typed tool, and a loop with a maximum number of turns. Claude can request a tool through atool_use block. Your Python code executes only approved functions, sends a tool_result back, and lets Claude decide whether it is done.

You need Python 3.10+, an Anthropic API key, and the anthropic package. Keep this page focused on Claude's native API; the general AI agent tutorial shows the provider-neutral version.

1. Set Up Anthropic

Install the SDK and keep the key outside your source code:

python3 -m venv .venv
source .venv/bin/activate
pip install anthropic
export ANTHROPIC_API_KEY="your-key"

Use a current Claude model available to your account. The model name is configuration, not business logic, so you can change it without rewriting the loop.

2. Define a Safe Tool

Tool schemas are a permission boundary. Describe exactly what the function does, validate its arguments, and return a small structured result. This example uses a deterministic local lookup so it is safe to run while learning.

from anthropic import Anthropic

client = Anthropic()

TOOLS = [{
    "name": "get_weather",
    "description": "Return today's weather for a supported city.",
    "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
        "additionalProperties": False,
    },
}]

WEATHER = {
    "London": {"temperature_c": 18, "condition": "cloudy"},
    "Tokyo": {"temperature_c": 27, "condition": "clear"},
}

def get_weather(city: str) -> dict:
    if city not in WEATHER:
        raise ValueError("Unsupported city")
    return WEATHER[city]

In a real integration, add network timeouts, authentication, rate limits, and response validation. Never let a model construct arbitrary SQL, shell commands, or file paths without a separate policy layer.

3. Run the Claude Agent Loop

Claude returns a list of content blocks. When one istool_use, execute the matching function and append atool_result message. When there is no tool request, return Claude's text. The step cap protects you from a prompt or tool that keeps the agent busy forever.

import json

def run_agent(goal: str, max_steps: int = 6) -> str:
    messages = [{"role": "user", "content": goal}]
    for _ in range(max_steps):
        response = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=800,
            system="Use get_weather only when it helps answer the goal. "
                   "Ask for clarification instead of guessing.",
            tools=TOOLS,
            messages=messages,
        )
        messages.append({"role": "assistant", "content": response.content})
        calls = [block for block in response.content
                 if block.type == "tool_use"]
        if not calls:
            return "".join(
                block.text for block in response.content
                if block.type == "text"
            )
        results = []
        for call in calls:
            if call.name != "get_weather":
                raise ValueError("Unknown tool requested")
            try:
                result = get_weather(call.input["city"])
                content = json.dumps(result)
                is_error = False
            except (KeyError, TypeError, ValueError) as exc:
                content = str(exc)
                is_error = True
            results.append({
                "type": "tool_result",
                "tool_use_id": call.id,
                "content": content,
                "is_error": is_error,
            })
        messages.append({"role": "user", "content": results})
    raise RuntimeError("Claude agent reached its step limit")

A request such as "What is the weather in Tokyo?" normally produces one tool call followed by a final response. Multiple tool calls can happen in one response; execute only names on your allowlist and send each result with its matching tool_use_id.

4. Test and Harden It

  1. Unit-test get_weather with a supported and unknown city.
  2. Test malformed tool input and an unknown tool name.
  3. Assert the loop stops at max_steps and times out on slow tools.
  4. Record model calls, latency, token usage, and tool errors for each run.
  5. Require human confirmation before any irreversible action.

Treat web pages and retrieved documents as untrusted instructions. A prompt injection can tell Claude to ignore your system message, so enforce permissions in Python rather than trusting the model to behave. For production telemetry, see the AI agent cost tracking guide.

Frequently Asked Questions

Can Claude call tools in an AI agent?

Yes. Anthropic's Messages API returns tool_use blocks and accepts matching tool_result blocks. Your application, not Claude, executes the function.

Does this Claude agent run forever?

No. The example enforces a maximum number of steps. Add time, token, and financial budgets as well, and fail closed when a budget is reached.

Should I use an agent framework with Claude?

Start with the native API when you are learning tool use. Add a framework when you need durable state, tracing, retries, or a larger workflow; keep the tool permission layer in your own code.

Next Steps

Read the general create-your-own-agent guide for memory and deployment patterns, or compare runtimes in the best AI agent framework 2026 ranking.

Add memory and deployment →

how to build an ai agent with claude — return to the complete AI agent architecture guide.

Was this helpful?

Your feedback stays on this page — no tracking.

Share this page