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#

  1. Define tools: Create tool objects with name, description, and JSON Schema
  2. Implement executors: Write functions that handle each tool
  3. Send request: Include tools in the API call
  4. Check for tool use: Look for tool_use blocks in the response
  5. Execute and return: Run the tool and send results back
  6. Continue or complete: Let the model decide if more tools are needed

Best Practices#

  1. Clear descriptions - Write detailed tool descriptions to help the model choose correctly
  2. Validate inputs - Use Zod or JSON Schema validation before executing
  3. Handle errors - Return error messages as tool results, not exceptions
  4. Set iteration limits - Prevent infinite loops with maxIterations
  5. Log tool calls - Track what tools are being called for debugging
  6. Keep tools focused - Each tool should do one thing well
  7. Use meaningful names - Tool names should clearly indicate their purpose
  8. Document parameters - Add descriptions to each parameter in the schema