How to Build an AI Agent with Claude Tool Use
What "building an AI agent" actually means
An AI agent, in the Claude context, is a loop: the model receives a task, decides which tool to call, you execute that tool in your own code, feed the result back, and repeat until the model has enough information to answer. Claude's tool use (also called function calling) is what makes this loop possible — it lets the model output a structured request to call a function instead of just generating text.
This guide walks through the full pattern: defining tools, handling the request/response cycle, managing multi-step reasoning, and avoiding the mistakes that make agents flaky in production. By the end you'll have a working agent loop you can extend with your own tools.
The core building blocks
Every Claude-based agent has four parts:
- A system prompt that defines the agent's role and constraints
- Tool definitions — JSON schemas describing what each tool does and what parameters it takes
- An execution loop that calls Claude, checks if it requested a tool, runs that tool, and sends the result back
- State — conversation history that accumulates across turns so the agent remembers what it already tried
Claude doesn't execute anything itself. It only decides what to call and with what arguments. Your code is responsible for actually running the function, calling the external API, querying the database, or whatever the tool represents.
Step 1: Define your tools
Tools are described with a name, a description, and a JSON schema for the input. The description matters more than people expect — Claude uses it to decide when to call the tool, not just how.
{
"name": "get_weather",
"description": "Get the current weather for a given city. Use this whenever the user asks about weather, temperature, or conditions in a specific location.",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name, e.g. 'Berlin'" }
},
"required": ["city"]
}
}
Keep tool descriptions specific. Vague descriptions like "gets data" lead to the model either over-calling or under-calling the tool. Write them as if you're briefing a new engineer who has never seen your system before.
Step 2: Send the request and inspect the response
Send the user's message along with your tool definitions. Claude will either answer directly or respond with a tool_use block containing the tool name and arguments it wants to invoke.
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 1024,
tools: [weatherTool, searchTool],
messages: [{ role: "user", content: "What's the weather in Lisbon right now?" }]
})
});
const data = await response.json();
Check data.stop_reason. If it's tool_use, the model wants to call a function — you'll find the tool name and parsed input inside data.content.
Step 3: Execute the tool and send the result back
This is the part developers skip in tutorials and then get stuck on in real projects: you have to append the tool call and your tool's result back into the message history, then call the model again.
if (data.stop_reason === "tool_use") {
const toolCall = data.content.find(block => block.type === "tool_use");
const result = await runTool(toolCall.name, toolCall.input);
messages.push({ role: "assistant", content: data.content });
messages.push({
role: "user",
content: [{
type: "tool_result",
tool_use_id: toolCall.id,
content: JSON.stringify(result)
}]
});
const followUp = await callClaude(messages, tools);
}
This exchange can happen multiple times in a row — the model might call one tool, look at the result, then call a second tool before finally answering. Your loop needs to keep running until stop_reason comes back as end_turn.
Step 4: Build the loop, not just a single call
A real agent wraps steps 2 and 3 in a while loop with a cap on iterations so a misbehaving tool chain doesn't run forever:
async function runAgent(userMessage, tools, maxSteps = 6) {
const messages = [{ role: "user", content: userMessage }];
for (let i = 0; i < maxSteps; i++) {
const data = await callClaude(messages, tools);
if (data.stop_reason !== "tool_use") {
return data.content; // final answer
}
const toolCall = data.content.find(b => b.type === "tool_use");
const result = await runTool(toolCall.name, toolCall.input);
messages.push({ role: "assistant", content: data.content });
messages.push({
role: "user",
content: [{ type: "tool_result", tool_use_id: toolCall.id, content: JSON.stringify(result) }]
});
}
throw new Error("Agent exceeded max steps");
}
This is the skeleton behind almost every Claude-based agent — research assistants, support bots, coding assistants, data pipeline agents. The complexity you add on top is mostly about which tools you give it and how well you constrain them.
Common mistakes that break agents
- Too many tools at once. Giving Claude 20 overlapping tools makes selection unreliable. Group related tools or route requests to subsets of tools based on intent.
- No iteration limit. Without a
maxStepscap, a bad tool result can send the model into a retry loop that burns tokens. - Returning raw errors as tool results. If a tool fails, return a clear error string like
"City not found"instead of a stack trace — the model will usually recover and ask a clarifying question or try differently. - Forgetting to persist state between requests. If your agent runs across multiple HTTP requests (e.g. a chat session), you need to store and reload the message history, not just the last turn.
Shipping it behind an API
Once the loop works locally, the next problem is usually operational: API keys per environment, streaming partial output to a frontend, and tracking token usage per customer if you're billing for agent runs. SubToAPI wraps Claude access behind a standard HTTPS API with sub_live_ application keys, so you can issue a separate key per environment or customer, stream responses, and see tool-use and token metadata per request from one dashboard. Check the tool use docs and the quickstart if you're wiring this into an existing backend, or start a free trial to test the full loop against a live key.
Questions
Do I need a framework to build a Claude agent? No. The agent loop described above is the entire pattern used by most agent frameworks internally. A framework helps once you have many tools, retries, or multi-agent coordination, but a single while loop is enough for most use cases.
How many tools can I give Claude in one request? There's no hard cap, but accuracy drops as tool count and overlap increase. Keep each request scoped to 5–10 clearly distinct tools where possible, and split unrelated capabilities into separate calls or agents.
What's the difference between tool use and streaming? Tool use is about letting the model call functions; streaming is about receiving the response token-by-token instead of all at once. They're independent features and can be combined — see streaming docs for details on handling both together.