What Are AI Agents and How Do They Differ From Chatbots?
How Do You Build the Core Agent Loop in Node.js?
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const tools = [
{
name: 'search_orders',
description: 'Search customer orders by email or order ID',
input_schema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Email or order ID' },
status: { type: 'string', enum: ['pending', 'shipped', 'delivered'] }
},
required: ['query']
}
}
];
async function runAgent(userQuery) {
let messages = [{ role: 'user', content: userQuery }];
while (true) {
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
tools,
messages
});
if (response.stop_reason === 'end_turn') {
return response.content.find(b => b.type === 'text')?.text;
}
const toolBlocks = response.content.filter(b => b.type === 'tool_use');
if (toolBlocks.length === 0) break;
messages.push({ role: 'assistant', content: response.content });
const toolResults = [];
for (const block of toolBlocks) {
const result = await executeTool(block.name, block.input);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(result)
});
}
messages.push({ role: 'user', content: toolResults });
}
}while(true) loop is deliberate. One user message can trigger five tool calls before the agent responds — searching orders, checking shipping status, looking up refund eligibility, processing the refund, then generating a confirmation message.How Should You Structure Tool Calling With Claude API?
executeTool function holds your business logic. According to a 2025 Retool survey, teams that isolate business logic in tool handlers ship 2.3x faster than those who embed it in the agent loop itself.async function executeTool(name, input) {
const toolHandlers = {
search_orders: async ({ query, status }) => {
const orders = await db.orders.findMany({
where: {
OR: [{ email: query }, { id: query }],
...(status && { status })
},
take: 10
});
return { orders, count: orders.length };
},
process_refund: async ({ order_id, reason }) => {
const order = await db.orders.findUnique({
where: { id: order_id }
});
if (!order) return { error: 'Order not found' };
if (order.status === 'refunded')
return { error: 'Already refunded' };
const refund = await stripe.refunds.create({
payment_intent: order.paymentIntentId
});
await db.orders.update({
where: { id: order_id },
data: { status: 'refunded', refundReason: reason }
});
return { success: true, refund_id: refund.id };
}
};
const handler = toolHandlers[name];
if (!handler) return { error: `Unknown tool: ${name}` };
try {
return await handler(input);
} catch (err) {
return { error: err.message };
}
}{ error: 'Already refunded' } and explains it naturally to the user. And every tool validates inputs before executing side effects. The refund handler checks existence and status before calling Stripe. Never trust the LLM to validate — validate in the handler.What Makes AI Agent Error Handling Production-Ready?
async function runAgentWithGuards(userQuery, maxIterations = 10) {
let messages = [{ role: 'user', content: userQuery }];
let iterations = 0;
while (iterations < maxIterations) {
iterations++;
let response;
try {
response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024, tools, messages
});
} catch (err) {
if (err.status === 429) {
const delay = Math.min(
1000 * Math.pow(2, iterations), 30000
);
await new Promise(r => setTimeout(r, delay));
iterations--;
continue;
}
throw new Error('Agent failed: ' + err.message);
}
if (response.stop_reason === 'end_turn') {
return {
answer: response.content.find(
b => b.type === 'text'
)?.text,
iterations,
tokensUsed: response.usage.input_tokens
+ response.usage.output_tokens
};
}
const toolBlocks = response.content.filter(
b => b.type === 'tool_use'
);
if (toolBlocks.length === 0) break;
messages.push({
role: 'assistant', content: response.content
});
const toolResults = [];
for (const block of toolBlocks) {
try {
const result = await executeTool(
block.name, block.input
);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(result)
});
} catch (toolErr) {
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify({
error: toolErr.message
}),
is_error: true
});
}
}
messages.push({ role: 'user', content: toolResults });
}
return {
answer: 'Could not complete within allowed steps.',
maxedOut: true
};
}What Are the Best Agentic AI Architecture Patterns for SaaS?
Can You Combine AI Agents With No-Code Automation?
What Does a Production AI Agent Architecture Look Like?
API Gateway (Express/Fastify)
— Rate limiting, auth, request queue
|
Router Agent (Claude Haiku)
— Intent classification in ~200ms
|
├── Support Agent (Sonnet) — 6 tools
| tickets, orders, FAQ, refunds
├── Sales Agent (Sonnet) — 4 tools
| CRM, calendar, email templates
└── Ops Agent (Sonnet) — 8 tools
deploys, logs, alerts, rollbacks
|
Tool Execution Layer (shared)
— Postgres, Stripe, SendGrid, Slack
|
Observability Layer
— tokens/conversation, latency, cost/resolution















