Production best practices for building AI agents with Vercel AI SDK v5. Covers security, performance, error handling, testing, deployment patterns, and real-world implementation guidelines.
Production best practices for building AI agents with Vercel AI SDK v5. Covers security, performance, error handling, testing, deployment patterns, and real-world implementation guidelines.
AI SDK v5 Best Practices
Comprehensive guide for building production-ready AI agents with Vercel AI SDK v5.
Core Principles
1. The No-Nonsense Approach (Vercel's Philosophy)
1. Prototype by hand → Understand the problem
2. Automate the loop → Let LLM handle decisions
3. Optimize for reliability → Add guardrails, fallbacks
Key insight: Use LLMs for judgment, plain code for deterministic logic.
// BAD: LLM for deterministic taskconst { text } = awaitgenerateText({
prompt: 'Calculate 2 + 2'
});
// GOOD: Code for deterministic, LLM for judgmentconst sum = 2 + 2;
const { text } = awaitgenerateText({
prompt: `Explain why ${sum} is the answer`
});
import { generateText, tool } from'ai';
import { z } from'zod';
// Tools that can run in parallelconst tools = {
fetchWeather: tool({
description: 'Get weather for a city',
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => fetchWeather(city)
}),
fetchNews: tool({
description: 'Get news for a topic',
parameters: z.object({ topic: z.string() }),
execute: async ({ topic }) => fetchNews(topic)
})
};
// AI SDK automatically parallelizes independent tool callsconst { text } = awaitgenerateText({
model: openai('gpt-4o'),
tools,
prompt: 'What\'s the weather in NYC and latest tech news?'// Both tools run in parallel when LLM requests them together
});
Context Window Management
import { generateText } from'ai';
// Estimate tokens (rough: 1 token ≈ 4 chars)functionestimateTokens(text: string): number {
returnMath.ceil(text.length / 4);
}
// Truncate to fit contextfunctionfitToContext(messages: string[],
maxTokens: number,
reserveForResponse: number = 1000): string[] {
const available = maxTokens - reserveForResponse;
constresult: string[] = [];
let used = 0;
// Keep most recent messagesfor (let i = messages.length - 1; i >= 0; i--) {
const tokens = estimateTokens(messages[i]);
if (used + tokens > available) break;
result.unshift(messages[i]);
used += tokens;
}
return result;
}
import { createMockModel } from'./test-utils';
// Create mock model for testingfunctioncreateMockModel(responses: string[]) {
let index = 0;
return {
doGenerate: async () => ({
text: responses[index++] || 'Default response'
})
};
}
// Test with mockit('should handle multi-turn conversation', async () => {
const mockModel = createMockModel([
'Hello! How can I help?',
'I can help with that task.'
]);
const result1 = awaitgenerateText({ model: mockModel, prompt: 'Hi' });
const result2 = awaitgenerateText({ model: mockModel, prompt: 'Help me' });
expect(result1.text).toBe('Hello! How can I help?');
expect(result2.text).toBe('I can help with that task.');
});