vercel-ai-sdk
npx skills add https://github.com/dsantiagomj/dsmj-ai-toolkit --skill vercel-ai-sdk
Agent 安装分布
Skill 文档
Vercel AI SDK – Build AI-Powered Apps
Stream AI responses, call functions, and build conversational interfaces
When to Use
Use Vercel AI SDK when you need:
- Streaming responses from LLMs with real-time UI updates
- React hooks (useChat, useCompletion) for chat/completion interfaces
- Function calling and tool use for AI agents
- Structured outputs with Zod schema validation
- Multi-provider support (OpenAI, Anthropic, Google, etc.)
- Edge runtime compatibility for fast global responses
Choose alternatives when:
- Building non-JavaScript/TypeScript applications
- Need direct provider SDKs for specialized features
- Not using streaming (simple REST API might suffice)
- Building complex agent frameworks (consider LangChain, AutoGPT)
Critical Patterns
Pattern 1: Streaming with Error Handling
// â
Good: Proper error handling and loading states
'use client';
import { useChat } from 'ai/react';
export function Chat() {
const {
messages,
input,
handleInputChange,
handleSubmit,
isLoading,
error,
reload,
} = useChat({
api: '/api/chat',
onError: (error) => {
console.error('Chat error:', error);
toast.error('Failed to send message');
},
onFinish: (message) => {
console.log('Message completed:', message);
},
});
return (
<div>
<div className="messages">
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong> {message.content}
</div>
))}
{isLoading && <div className="loading">AI is thinking...</div>}
{error && (
<div className="error">
<p>Error: {error.message}</p>
<button onClick={() => reload()}>Retry</button>
</div>
)}
</div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={handleInputChange}
disabled={isLoading}
placeholder="Type a message..."
/>
<button type="submit" disabled={isLoading || !input.trim()}>
Send
</button>
</form>
</div>
);
}
// â Bad: No error handling, no loading states
export function BadChat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<div>
{messages.map((m) => <div key={m.id}>{m.content}</div>)}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button>Send</button>
</form>
</div>
);
}
Why: Error handling improves UX; loading states provide feedback; retry gives users control.
Pattern 2: Tool Calling with UI Feedback
// â
Good: Show tool calls in UI, handle execution properly
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4-turbo'),
messages,
tools: {
getWeather: tool({
description: 'Get current weather for a location',
parameters: z.object({
location: z.string().describe('City and state, e.g. San Francisco, CA'),
unit: z.enum(['celsius', 'fahrenheit']).default('fahrenheit'),
}),
execute: async ({ location, unit }) => {
const weather = await fetchWeatherAPI(location, unit);
return {
location,
temperature: weather.temp,
conditions: weather.conditions,
unit,
};
},
}),
},
maxSteps: 5,
});
return result.toDataStreamResponse();
}
// Client: Display tool calls
'use client';
export function ChatWithTools() {
const { messages } = useChat({ api: '/api/chat' });
return (
<div>
{messages.map((message) => (
<div key={message.id}>
<div>{message.content}</div>
{message.toolInvocations?.map((tool, i) => (
<div key={i} className="tool-call">
{tool.state === 'call' && (
<p>Calling {tool.toolName}...</p>
)}
{tool.state === 'result' && (
<div>
<p>Used {tool.toolName}</p>
<pre>{JSON.stringify(tool.result, null, 2)}</pre>
</div>
)}
</div>
))}
</div>
))}
</div>
);
}
Why: Showing tool calls builds trust; users understand AI’s actions; debugging is easier.
Pattern 3: Structured Outputs with Validation
// â
Good: Use generateObject for structured data
// app/api/extract/route.ts
import { openai } from '@ai-sdk/openai';
import { generateObject } from 'ai';
import { z } from 'zod';
const RecipeSchema = z.object({
name: z.string().describe('Recipe name'),
ingredients: z.array(
z.object({
name: z.string(),
amount: z.string(),
unit: z.string().optional(),
})
),
steps: z.array(z.string()).min(1),
prepTime: z.number().describe('Prep time in minutes'),
cookTime: z.number().describe('Cook time in minutes'),
servings: z.number().positive(),
difficulty: z.enum(['easy', 'medium', 'hard']),
});
export async function POST(req: Request) {
const { prompt } = await req.json();
try {
const { object } = await generateObject({
model: openai('gpt-4-turbo'),
schema: RecipeSchema,
prompt: `Extract recipe information: ${prompt}`,
});
return Response.json({ success: true, data: object });
} catch (error) {
return Response.json(
{ success: false, error: 'Failed to extract recipe' },
{ status: 500 }
);
}
}
Why: generateObject ensures valid output; Zod schema provides type safety; reduces parsing errors.
For complete streaming and hook examples, see references/streaming.md.
Anti-Patterns
â Anti-Pattern 1: Not Streaming When Beneficial
Don’t do this:
// â Using generateText instead of streamText
export async function POST(req: Request) {
const { messages } = await req.json();
const { text } = await generateText({
model: openai('gpt-4-turbo'),
messages,
});
return Response.json({ text }); // User waits for entire response
}
Why it’s wrong: Poor UX; long wait times; higher perceived latency.
Do this instead:
// â
Stream for better UX
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4-turbo'),
messages,
});
return result.toDataStreamResponse();
}
â Anti-Pattern 2: Exposing API Keys Client-Side
Don’t do this:
// â Using OpenAI directly from client
'use client';
import OpenAI from 'openai';
export function Chat() {
const openai = new OpenAI({
apiKey: process.env.NEXT_PUBLIC_OPENAI_KEY, // EXPOSED!
});
}
Why it’s wrong: API keys exposed in browser; security risk; quota abuse.
Do this instead:
// â
Use API routes (server-side)
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4-turbo'),
messages,
});
return result.toDataStreamResponse();
}
// Client calls your API
'use client';
export function Chat() {
const { messages } = useChat({ api: '/api/chat' });
// API key never exposed
}
â Anti-Pattern 3: No Token/Cost Limits
Don’t do this:
// â No limits on token usage
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4-turbo'),
messages,
// No maxTokens, no checks
});
return result.toDataStreamResponse();
}
Why it’s wrong: Runaway costs; unpredictable bills.
Do this instead:
// â
Set limits and validate input
export async function POST(req: Request) {
const { messages } = await req.json();
if (messages.length > 50) {
return Response.json({ error: 'Too many messages' }, { status: 400 });
}
const totalLength = messages.reduce((sum, msg) => sum + msg.content.length, 0);
if (totalLength > 10000) {
return Response.json({ error: 'Messages too long' }, { status: 400 });
}
const result = streamText({
model: openai('gpt-4-turbo'),
messages,
maxTokens: 1000, // Limit response length
});
return result.toDataStreamResponse();
}
For more anti-patterns and solutions, see references/best-practices.md.
Quick Reference
React Hooks
// useChat
const { messages, input, handleInputChange, handleSubmit, isLoading, error, reload } = useChat({
api: '/api/chat',
initialMessages: [],
onFinish: (message) => {},
onError: (error) => {},
});
// useCompletion
const { completion, input, handleInputChange, handleSubmit, isLoading } = useCompletion({
api: '/api/completion',
});
Server Functions
// streamText
const result = streamText({
model: openai('gpt-4-turbo'),
messages: [],
system: 'System message',
tools: {},
maxSteps: 5,
temperature: 0.7,
maxTokens: 1000,
});
return result.toDataStreamResponse();
// generateObject
const { object } = await generateObject({
model: openai('gpt-4-turbo'),
schema: z.object({...}),
prompt: 'Extract data',
});
Learn More
- Streaming & Hooks: references/streaming.md – Complete useChat, useCompletion examples
- Advanced Patterns: references/advanced.md – Multiple providers, abort requests, useAssistant
- Full Examples: references/examples.md – Tool calls UI, structured outputs, error handling
- Best Practices: references/best-practices.md – Security, cost optimization
External References
Maintained by dsmj-ai-toolkit