Function Calling Pattern
Enable AI models to use tools and execute functions to interact with external systems and data.
Overview#
Function calling (also called tool use) allows AI models to request the execution of predefined functions. The model decides which functions to call based on the conversation, and your application executes them and returns results.
When to use:
- AI agents with capabilities
- Database queries via natural language
- External API integration
- Multi-step reasoning tasks
- Automating workflows
Key features:
- Tool definition with JSON Schema
- Tool execution handling
- Multi-tool conversations
- Iterative tool loops
- Error handling
Code Example#
Tool Definition#
1// lib/ai/tools.ts
2import { anthropic } from '@/lib/anthropic'
3
4const tools = [
5 {
6 name: 'get_weather',
7 description: 'Get the current weather for a location',
8 input_schema: {
9 type: 'object',
10 properties: {
11 location: {
12 type: 'string',
13 description: 'City and state, e.g., San Francisco, CA'
14 }
15 },
16 required: ['location']
17 }
18 },
19 {
20 name: 'search_database',
21 description: 'Search the product database',
22 input_schema: {
23 type: 'object',
24 properties: {
25 query: { type: 'string' },
26 limit: { type: 'number', default: 10 }
27 },
28 required: ['query']
29 }
30 }
31]Tool Execution#
1// lib/ai/tools.ts
2import { prisma } from '@/lib/db'
3
4async function executeTool(name: string, input: any) {
5 switch (name) {
6 case 'get_weather':
7 // Call weather API
8 const weatherResponse = await fetch(
9 `https://api.weather.com/v1/current?location=${encodeURIComponent(input.location)}`
10 )
11 const weather = await weatherResponse.json()
12 return JSON.stringify({
13 temperature: weather.temp,
14 condition: weather.condition
15 })
16
17 case 'search_database':
18 // Search database
19 const products = await prisma.product.findMany({
20 where: {
21 OR: [
22 { name: { contains: input.query, mode: 'insensitive' } },
23 { description: { contains: input.query, mode: 'insensitive' } }
24 ]
25 },
26 take: input.limit ?? 10
27 })
28 return JSON.stringify(products)
29
30 default:
31 throw new Error(`Unknown tool: ${name}`)
32 }
33}Chat with Tools#
1// lib/ai/tools.ts
2export async function chatWithTools(prompt: string) {
3 const response = await anthropic.messages.create({
4 model: 'claude-sonnet-4-20250514',
5 max_tokens: 1024,
6 tools,
7 messages: [{ role: 'user', content: prompt }]
8 })
9
10 // Check for tool use
11 for (const block of response.content) {
12 if (block.type === 'tool_use') {
13 const result = await executeTool(block.name, block.input)
14
15 // Continue conversation with tool result
16 const followUp = await anthropic.messages.create({
17 model: 'claude-sonnet-4-20250514',
18 max_tokens: 1024,
19 messages: [
20 { role: 'user', content: prompt },
21 { role: 'assistant', content: response.content },
22 {
23 role: 'user',
24 content: [
25 {
26 type: 'tool_result',
27 tool_use_id: block.id,
28 content: result
29 }
30 ]
31 }
32 ]
33 })
34
35 return followUp
36 }
37 }
38
39 return response
40}Multi-Tool Loop#
1// lib/ai/tools.ts
2export async function chatWithToolLoop(
3 prompt: string,
4 maxIterations = 5
5) {
6 let messages: any[] = [{ role: 'user', content: prompt }]
7 let iteration = 0
8
9 while (iteration < maxIterations) {
10 const response = await anthropic.messages.create({
11 model: 'claude-sonnet-4-20250514',
12 max_tokens: 1024,
13 tools,
14 messages
15 })
16
17 // Check if we're done
18 if (response.stop_reason === 'end_turn') {
19 const textBlock = response.content.find(b => b.type === 'text')
20 return {
21 response: textBlock?.text ?? '',
22 iterations: iteration
23 }
24 }
25
26 // Process tool uses
27 const toolResults = []
28 for (const block of response.content) {
29 if (block.type === 'tool_use') {
30 try {
31 const result = await executeTool(block.name, block.input)
32 toolResults.push({
33 type: 'tool_result',
34 tool_use_id: block.id,
35 content: result
36 })
37 } catch (error) {
38 toolResults.push({
39 type: 'tool_result',
40 tool_use_id: block.id,
41 content: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
42 is_error: true
43 })
44 }
45 }
46 }
47
48 // Add to conversation
49 messages.push({ role: 'assistant', content: response.content })
50 messages.push({ role: 'user', content: toolResults })
51
52 iteration++
53 }
54
55 throw new Error('Max iterations reached')
56}API Route with Tools#
1// app/api/chat/route.ts
2import { NextRequest, NextResponse } from 'next/server'
3import { chatWithToolLoop } from '@/lib/ai/tools'
4import { auth } from '@/lib/auth'
5
6export async function POST(request: NextRequest) {
7 const { userId } = await auth()
8 if (!userId) {
9 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
10 }
11
12 const { message } = await request.json()
13
14 try {
15 const result = await chatWithToolLoop(message)
16 return NextResponse.json(result)
17 } catch (error) {
18 console.error('Chat error:', error)
19 return NextResponse.json(
20 { error: 'Failed to process request' },
21 { status: 500 }
22 )
23 }
24}Typed Tool Definitions#
1// lib/ai/tools/definitions.ts
2import { z } from 'zod'
3
4// Define tool schemas with Zod
5const WeatherInput = z.object({
6 location: z.string().describe('City and state, e.g., San Francisco, CA'),
7 units: z.enum(['celsius', 'fahrenheit']).default('fahrenheit')
8})
9
10const SearchInput = z.object({
11 query: z.string().describe('Search query'),
12 category: z.string().optional().describe('Filter by category'),
13 limit: z.number().min(1).max(50).default(10)
14})
15
16// Type-safe tool interface
17interface Tool<T extends z.ZodType> {
18 name: string
19 description: string
20 schema: T
21 execute: (input: z.infer<T>) => Promise<string>
22}
23
24// Create tool with type inference
25function createTool<T extends z.ZodType>(tool: Tool<T>) {
26 return {
27 name: tool.name,
28 description: tool.description,
29 input_schema: zodToJsonSchema(tool.schema),
30 execute: async (input: unknown) => {
31 const validated = tool.schema.parse(input)
32 return tool.execute(validated)
33 }
34 }
35}
36
37// Usage
38const weatherTool = createTool({
39 name: 'get_weather',
40 description: 'Get current weather for a location',
41 schema: WeatherInput,
42 execute: async (input) => {
43 // input is typed as { location: string, units: 'celsius' | 'fahrenheit' }
44 const data = await fetchWeather(input.location, input.units)
45 return JSON.stringify(data)
46 }
47})Usage Instructions#
- Define tools: Create tool objects with name, description, and JSON Schema
- Implement executors: Write functions that handle each tool
- Send request: Include tools in the API call
- Check for tool use: Look for
tool_useblocks in the response - Execute and return: Run the tool and send results back
- Continue or complete: Let the model decide if more tools are needed
Best Practices#
- Clear descriptions - Write detailed tool descriptions to help the model choose correctly
- Validate inputs - Use Zod or JSON Schema validation before executing
- Handle errors - Return error messages as tool results, not exceptions
- Set iteration limits - Prevent infinite loops with maxIterations
- Log tool calls - Track what tools are being called for debugging
- Keep tools focused - Each tool should do one thing well
- Use meaningful names - Tool names should clearly indicate their purpose
- Document parameters - Add descriptions to each parameter in the schema
Related Patterns#
- AI Agents - Build autonomous agents with tools
- Claude API - Basic Claude integration
- Streaming - Stream tool-using responses