Claude Tool Use Tutorial: Building AI Agents with Function Calling
A complete guide to Claude's tool use API — defining tools, implementing the agentic loop, handling parallel tool calls, and building a practical AI assistant that can query real data and take actions.
Claude's tool use API is what transforms a conversational model into a capable AI agent. Instead of generating text that describes actions, Claude actually calls your functions, gets real results, and incorporates live data into its responses. This guide builds a complete, practical agent from tool definition through the agentic loop — with real code you can adapt for production.
How Tool Use Works
The flow has three steps repeated in a loop: (1) send Claude a message with your tool definitions; (2) if Claude returns stop_reason: 'tool_use', execute the requested tool and send the result back; (3) repeat until Claude returns stop_reason: 'end_turn' with a final text response. Each round-trip is a full API call — Claude reasons about the results before deciding its next step.
Defining Tools
Tools are JSON Schema objects. The description field is critical — Claude uses it to decide when to call the tool and how to construct the arguments. Be specific and include examples. Vague descriptions lead to wrong tool selection.
const tools = [
{
name: 'search_listings',
description:
'Search marketplace listings by keyword, category, and price range. ' +
'Use this when the user asks about available items, services, or providers. ' +
'Example: search_listings({ query: "photography", category: "services", maxPrice: 500 })',
input_schema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search keyword or phrase',
},
category: {
type: 'string',
enum: ['services', 'rentals', 'products'],
description: 'Listing category to filter by',
},
maxPrice: {
type: 'number',
description: 'Maximum price in USD',
},
location: {
type: 'string',
description: 'City or region to search in',
},
},
required: ['query'],
},
},
{
name: 'get_listing_details',
description:
'Get full details for a specific listing by its ID, including description, ' +
'pricing, availability, and provider profile. Use after search_listings ' +
'when the user wants more information about a specific result.',
input_schema: {
type: 'object',
properties: {
listing_id: {
type: 'string',
description: 'The listing ID from search results',
},
},
required: ['listing_id'],
},
},
{
name: 'check_availability',
description:
'Check whether a listing is available for a specific date range. ' +
'Use this before suggesting a booking to the user.',
input_schema: {
type: 'object',
properties: {
listing_id: { type: 'string' },
start_date: {
type: 'string',
description: 'ISO 8601 date string, e.g. 2026-07-15',
},
end_date: {
type: 'string',
description: 'ISO 8601 date string, e.g. 2026-07-20',
},
},
required: ['listing_id', 'start_date', 'end_date'],
},
},
];Executing Tool Calls
Map tool names to your actual implementation functions. Each tool executor receives the input object Claude generated and returns a result. Wrap every tool call in try/catch — a tool failure should never crash the agent loop.
async function executeTool(name, input) {
try {
switch (name) {
case 'search_listings':
return await searchListings(input);
case 'get_listing_details':
return await getListingById(input.listing_id);
case 'check_availability':
return await checkAvailability(
input.listing_id,
input.start_date,
input.end_date,
);
default:
return { error: `Unknown tool: ${name}` };
}
} catch (err) {
return { error: err.message };
}
}The Complete Agentic Loop
The agentic loop runs until Claude signals end_turn or a maximum iteration count is reached. In each iteration, execute all tool calls Claude requested — if there are multiple, run them in parallel with Promise.all for speed.
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
export async function runAgent(userMessage, systemPrompt = '') {
let messages = [{ role: 'user', content: userMessage }];
const MAX_ITERATIONS = 10;
for (let i = 0; i < MAX_ITERATIONS; i++) {
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 4096,
system: systemPrompt,
tools,
messages,
});
// Append Claude's full response to history
messages.push({ role: 'assistant', content: response.content });
// Done — return the text response
if (response.stop_reason === 'end_turn') {
const textBlock = response.content.find((b) => b.type === 'text');
return textBlock?.text ?? '';
}
// Execute all tool calls in parallel
if (response.stop_reason === 'tool_use') {
const toolUseBlocks = response.content.filter(
(b) => b.type === 'tool_use',
);
const toolResults = await Promise.all(
toolUseBlocks.map(async (block) => {
const result = await executeTool(block.name, block.input);
return {
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(result),
...(result.error ? { is_error: true } : {}),
};
}),
);
messages.push({ role: 'user', content: toolResults });
}
}
return 'The agent reached the maximum number of steps without completing.';
}Using the Agent in a Next.js Route Handler
Expose the agent through a Route Handler. Because the agentic loop involves multiple API round-trips, the total time can exceed 10–30 seconds for complex queries — use streaming or a job queue for long-running agents rather than blocking the HTTP response.
// app/api/agent/route.js
import { runAgent } from '@/lib/agent';
const SYSTEM = `You are a marketplace assistant. Help users find listings,
check availability, and answer questions about providers.
Always search for listings before giving recommendations.
Be concise — summarise results rather than listing every field.`;
export async function POST(req) {
const { message } = await req.json();
if (!message?.trim()) {
return Response.json({ error: 'Message required' }, { status: 400 });
}
const result = await runAgent(message, SYSTEM);
return Response.json({ response: result });
}Controlling Tool Behaviour
Claude's tool choice can be set explicitly. By default it's auto — Claude decides when to use tools. Set tool_choice: { type: 'any' } to force Claude to always use at least one tool (useful for structured data extraction). Set tool_choice: { type: 'tool', name: 'search_listings' } to force a specific tool. Set tool_choice: { type: 'none' } to prevent tool use entirely in a turn where you want a final summary.
Streaming an Agentic Response
For a better UX, stream intermediate status updates to the client while the agent works — 'Searching listings...', 'Checking availability...'. Use a ReadableStream and push status messages between tool execution rounds. This makes multi-step agents feel responsive even when total execution takes 10–15 seconds.
// Streaming status updates during agentic loop
const readable = new ReadableStream({
async start(controller) {
const send = (text) =>
controller.enqueue(new TextEncoder().encode(text));
let messages = [{ role: 'user', content: userMessage }];
for (let i = 0; i < MAX_ITERATIONS; i++) {
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 4096,
tools,
messages,
});
messages.push({ role: 'assistant', content: response.content });
if (response.stop_reason === 'end_turn') {
const text = response.content.find((b) => b.type === 'text')?.text ?? '';
send(text);
break;
}
if (response.stop_reason === 'tool_use') {
const toolBlocks = response.content.filter((b) => b.type === 'tool_use');
for (const block of toolBlocks) {
send(`[Calling ${block.name}...]\n`);
}
const results = await Promise.all(
toolBlocks.map(async (block) => ({
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(await executeTool(block.name, block.input)),
})),
);
messages.push({ role: 'user', content: results });
}
}
controller.close();
},
});Production Considerations
A few things that matter in production: (1) Log every tool call with its inputs and outputs — this is essential for debugging unexpected agent behaviour. (2) Validate tool inputs before executing — Claude's JSON Schema adherence is strong but not guaranteed for edge cases. (3) Add timeouts to individual tool calls so a slow database query can't stall the entire agent. (4) Design your tools to be idempotent where possible — agents sometimes retry tool calls on partial responses.
Further Reading
Frequently Asked Questions
What is Claude tool use?
Tool use (also called function calling) lets Claude call external functions you define. Claude decides which tools to use, generates the correct arguments, and you execute the function and return the result. Claude incorporates the result into its final response. This is how you build AI assistants that can query databases, call APIs, and take real actions.
What is the difference between tool use and just asking Claude to generate code?
With tool use, Claude actually calls your functions during the conversation — it's not just generating text. You execute the function server-side and feed the real result back to Claude. This means Claude gets live data from your database, current API responses, and can trigger real actions like sending emails or updating records.
Can Claude call multiple tools in a single response?
Yes. When Claude determines it needs multiple pieces of information, it returns multiple tool_use blocks in a single response. You can execute these in parallel, then return all results in a single tool_result message. This makes multi-step information gathering much faster.
How do I handle tool errors in Claude tool use?
If your tool execution fails, return the error message as the tool_result content with is_error: true. Claude will acknowledge the error and either try a different approach or explain to the user what went wrong. Never let a tool error crash your agentic loop.
How many tools can I define for Claude?
There is no hard limit on the number of tools. In practice, 5–15 well-defined tools work best. Too many tools can confuse the model's tool selection. Group related operations into a single tool with an action parameter rather than creating many similar tools.
What is the difference between tool use and computer use in Claude?
Tool use (function calling) lets Claude call your defined functions with structured JSON arguments. Computer use is a separate Claude capability that lets Claude control a computer UI — clicking, typing, and navigating graphical interfaces. Most production AI applications use tool use; computer use is for automating interfaces without APIs.
How do I prevent Claude from calling tools in an infinite loop?
Set a maximum iteration count on your agentic loop (typically 10 iterations). If Claude hasn't reached end_turn after that many rounds, return an error or partial result. This is rare with well-designed tools and clear tool descriptions, but the safeguard is important for production reliability.
More Articles
Need help with this?
I'm available for Sharetribe Flex, Shopify, Next.js, and AI integration projects.
Get In Touch