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#
- Define tools: Create tool objects with name, description, schema, and execute function
- Set up the agent: Initialize with a system prompt and available tools
- Run the loop: Let the agent decide which tools to use
- Handle results: Process tool outputs and continue until complete
- Set limits: Always set a maximum iteration count
Best Practices#
- Clear tool descriptions - Help the AI understand when to use each tool
- Set iteration limits - Prevent runaway agents with maxIterations
- Handle errors gracefully - Return error messages as tool results
- Log tool calls - Track what the agent is doing for debugging
- Use specific system prompts - Guide the agent's behavior
- Implement timeouts - Set time limits on tool execution
- Validate tool inputs - Check inputs before executing
- Stream for long tasks - Show progress during multi-step operations
Related Patterns#
- Function Calling - Tool definition and execution
- Streaming - Real-time response streaming
- Claude API - Basic Claude integration