AI Agents Pattern

Build autonomous AI agents that can reason, use tools, and complete multi-step tasks.

Overview#

AI agents are systems that can break down complex tasks, decide which tools to use, and iterate until they achieve a goal. They combine LLM reasoning with tool execution to handle tasks that require multiple steps or external interactions.

When to use:

  • Autonomous task completion
  • Research and information gathering
  • Multi-step workflows
  • Tool orchestration
  • Complex reasoning tasks

Key features:

  • Agent loop with tool execution
  • Multiple tool integration
  • Streaming agent responses
  • Error handling and recovery
  • Maximum iteration limits

Code Example#

Basic Agent Loop#

1// lib/agents/base.ts 2import Anthropic from '@anthropic-ai/sdk' 3 4const client = new Anthropic() 5 6interface Tool { 7 name: string 8 description: string 9 input_schema: object 10 execute: (input: any) => Promise<string> 11} 12 13export async function runAgent( 14 systemPrompt: string, 15 userMessage: string, 16 tools: Tool[], 17 maxIterations = 10 18) { 19 const messages: Anthropic.MessageParam[] = [ 20 { role: 'user', content: userMessage } 21 ] 22 23 const anthropicTools = tools.map(t => ({ 24 name: t.name, 25 description: t.description, 26 input_schema: t.input_schema 27 })) 28 29 for (let i = 0; i < maxIterations; i++) { 30 const response = await client.messages.create({ 31 model: 'claude-sonnet-4-20250514', 32 max_tokens: 4096, 33 system: systemPrompt, 34 tools: anthropicTools, 35 messages 36 }) 37 38 // Check if we're done 39 if (response.stop_reason === 'end_turn') { 40 const textBlock = response.content.find(b => b.type === 'text') 41 return textBlock?.text ?? '' 42 } 43 44 // Process tool uses 45 const toolUses = response.content.filter(b => b.type === 'tool_use') 46 47 if (toolUses.length === 0) { 48 const textBlock = response.content.find(b => b.type === 'text') 49 return textBlock?.text ?? '' 50 } 51 52 // Add assistant message 53 messages.push({ role: 'assistant', content: response.content }) 54 55 // Execute tools and add results 56 const toolResults = await Promise.all( 57 toolUses.map(async (toolUse) => { 58 const tool = tools.find(t => t.name === toolUse.name) 59 if (!tool) { 60 return { 61 type: 'tool_result' as const, 62 tool_use_id: toolUse.id, 63 content: `Error: Tool ${toolUse.name} not found` 64 } 65 } 66 67 try { 68 const result = await tool.execute(toolUse.input) 69 return { 70 type: 'tool_result' as const, 71 tool_use_id: toolUse.id, 72 content: result 73 } 74 } catch (error) { 75 return { 76 type: 'tool_result' as const, 77 tool_use_id: toolUse.id, 78 content: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` 79 } 80 } 81 }) 82 ) 83 84 messages.push({ role: 'user', content: toolResults }) 85 } 86 87 throw new Error('Max iterations reached') 88}

Research Agent#

1// lib/agents/research.ts 2import { runAgent } from './base' 3 4const webSearchTool = { 5 name: 'web_search', 6 description: 'Search the web for information', 7 input_schema: { 8 type: 'object', 9 properties: { 10 query: { type: 'string', description: 'The search query' } 11 }, 12 required: ['query'] 13 }, 14 execute: async (input: { query: string }) => { 15 const response = await fetch( 16 `https://api.search.com/search?q=${encodeURIComponent(input.query)}` 17 ) 18 const data = await response.json() 19 return JSON.stringify(data.results.slice(0, 5)) 20 } 21} 22 23const readUrlTool = { 24 name: 'read_url', 25 description: 'Read the contents of a URL', 26 input_schema: { 27 type: 'object', 28 properties: { 29 url: { type: 'string', description: 'The URL to read' } 30 }, 31 required: ['url'] 32 }, 33 execute: async (input: { url: string }) => { 34 const response = await fetch(input.url) 35 const html = await response.text() 36 // Extract text from HTML (simplified) 37 return html.replace(/<[^>]*>/g, '').slice(0, 5000) 38 } 39} 40 41export async function researchTopic(topic: string) { 42 const systemPrompt = `You are a research assistant. Your job is to research topics thoroughly using web search and URL reading tools. Provide comprehensive, well-sourced information.` 43 44 return runAgent( 45 systemPrompt, 46 `Research the following topic and provide a detailed summary: ${topic}`, 47 [webSearchTool, readUrlTool] 48 ) 49}

Task Execution Agent#

1// lib/agents/task.ts 2import { runAgent } from './base' 3import { prisma } from '@/lib/db' 4 5const createTaskTool = { 6 name: 'create_task', 7 description: 'Create a new task in the system', 8 input_schema: { 9 type: 'object', 10 properties: { 11 title: { type: 'string' }, 12 description: { type: 'string' }, 13 priority: { type: 'string', enum: ['low', 'medium', 'high'] }, 14 dueDate: { type: 'string', description: 'ISO date string' } 15 }, 16 required: ['title'] 17 }, 18 execute: async (input: any) => { 19 const task = await prisma.task.create({ 20 data: { 21 title: input.title, 22 description: input.description, 23 priority: input.priority ?? 'medium', 24 dueDate: input.dueDate ? new Date(input.dueDate) : null 25 } 26 }) 27 return `Created task: ${task.id}` 28 } 29} 30 31const listTasksTool = { 32 name: 'list_tasks', 33 description: 'List all tasks', 34 input_schema: { 35 type: 'object', 36 properties: { 37 status: { type: 'string', enum: ['pending', 'completed', 'all'] } 38 } 39 }, 40 execute: async (input: any) => { 41 const where = input.status === 'all' ? {} : { status: input.status } 42 const tasks = await prisma.task.findMany({ where }) 43 return JSON.stringify(tasks) 44 } 45} 46 47export async function processTaskRequest(userRequest: string) { 48 const systemPrompt = `You are a task management assistant. Help users create, list, and manage their tasks using the available tools.` 49 50 return runAgent( 51 systemPrompt, 52 userRequest, 53 [createTaskTool, listTasksTool] 54 ) 55}

Streaming Agent#

1// lib/agents/streaming.ts 2import Anthropic from '@anthropic-ai/sdk' 3 4interface Tool { 5 name: string 6 description: string 7 input_schema: object 8 execute: (input: any) => Promise<string> 9} 10 11export async function* streamingAgent( 12 systemPrompt: string, 13 userMessage: string, 14 tools: Tool[] 15) { 16 const client = new Anthropic() 17 const messages: Anthropic.MessageParam[] = [ 18 { role: 'user', content: userMessage } 19 ] 20 21 while (true) { 22 const stream = client.messages.stream({ 23 model: 'claude-sonnet-4-20250514', 24 max_tokens: 4096, 25 system: systemPrompt, 26 tools: tools.map(t => ({ 27 name: t.name, 28 description: t.description, 29 input_schema: t.input_schema 30 })), 31 messages 32 }) 33 34 let currentToolUse: any = null 35 36 for await (const event of stream) { 37 if (event.type === 'content_block_start' && event.content_block.type === 'text') { 38 yield { type: 'text_start' } 39 } 40 41 if (event.type === 'content_block_delta') { 42 if (event.delta.type === 'text_delta') { 43 yield { type: 'text', content: event.delta.text } 44 } 45 } 46 47 if (event.type === 'content_block_start' && event.content_block.type === 'tool_use') { 48 currentToolUse = event.content_block 49 yield { type: 'tool_start', name: currentToolUse.name } 50 } 51 } 52 53 const finalMessage = await stream.finalMessage() 54 55 if (finalMessage.stop_reason === 'end_turn') { 56 yield { type: 'done' } 57 break 58 } 59 60 // Handle tool execution and continue loop... 61 // Add tool results to messages and continue 62 } 63}

Usage Instructions#

  1. Define tools: Create tool objects with name, description, schema, and execute function
  2. Set up the agent: Initialize with a system prompt and available tools
  3. Run the loop: Let the agent decide which tools to use
  4. Handle results: Process tool outputs and continue until complete
  5. Set limits: Always set a maximum iteration count

Best Practices#

  1. Clear tool descriptions - Help the AI understand when to use each tool
  2. Set iteration limits - Prevent runaway agents with maxIterations
  3. Handle errors gracefully - Return error messages as tool results
  4. Log tool calls - Track what the agent is doing for debugging
  5. Use specific system prompts - Guide the agent's behavior
  6. Implement timeouts - Set time limits on tool execution
  7. Validate tool inputs - Check inputs before executing
  8. Stream for long tasks - Show progress during multi-step operations