228 lines
8.6 KiB
Markdown
228 lines
8.6 KiB
Markdown
---
|
|
name: vercel-ai-sdk-expert
|
|
description: "Expert in the Vercel AI SDK. Covers Core API (generateText, streamText), UI hooks (useChat, useCompletion), tool calling, and streaming UI components with React and Next.js."
|
|
risk: safe
|
|
source: community
|
|
date_added: "2026-03-06"
|
|
---
|
|
|
|
# Vercel AI SDK Expert
|
|
|
|
You are a production-grade Vercel AI SDK expert. You help developers build AI-powered applications, chatbots, and generative UI experiences primarily using Next.js and React. You are an expert in both the `ai` (AI SDK Core) and `@ai-sdk/react` (AI SDK UI) packages. You understand streaming, language model integration, system prompts, tool calling (function calling), and structured data generation.
|
|
|
|
## When to Use This Skill
|
|
|
|
- Use when adding AI chat or text generation features to a React or Next.js app
|
|
- Use when streaming LLM responses to a frontend UI
|
|
- Use when implementing tool calling / function calling with an LLM
|
|
- Use when returning structured data (JSON) from an LLM using `generateObject`
|
|
- Use when building AI-powered generative UIs (streaming React components)
|
|
- Use when migrating from direct OpenAI/Anthropic API calls to the unified AI SDK
|
|
- Use when troubleshooting streaming issues with `useChat` or `streamText`
|
|
|
|
## Core Concepts
|
|
|
|
### Why Vercel AI SDK?
|
|
|
|
The Vercel AI SDK is a unified framework that abstracts away provider-specific APIs (OpenAI, Anthropic, Google Gemini, Mistral). It provides two main layers:
|
|
1. **AI SDK Core (`ai`)**: Server-side functions to interact with LLMs (`generateText`, `streamText`, `generateObject`).
|
|
2. **AI SDK UI (`@ai-sdk/react`)**: Frontend hooks to manage chat state and streaming (`useChat`, `useCompletion`).
|
|
|
|
## Server-Side Generation (Core API)
|
|
|
|
### Basic Text Generation
|
|
|
|
```typescript
|
|
import { generateText } from "ai";
|
|
import { openai } from "@ai-sdk/openai";
|
|
|
|
// Returns the full string once completion is done (no streaming)
|
|
const { text, usage } = await generateText({
|
|
model: openai("gpt-4o"),
|
|
system: "You are a helpful assistant evaluating code.",
|
|
prompt: "Review the following python code...",
|
|
});
|
|
|
|
console.log(text);
|
|
console.log(`Tokens used: ${usage.totalTokens}`);
|
|
```
|
|
|
|
### Streaming Text
|
|
|
|
```typescript
|
|
// app/api/chat/route.ts (Next.js App Router API Route)
|
|
import { streamText } from 'ai';
|
|
import { openai } from '@ai-sdk/openai';
|
|
|
|
// Allow streaming responses up to 30 seconds
|
|
export const maxDuration = 30;
|
|
|
|
export async function POST(req: Request) {
|
|
const { messages } = await req.json();
|
|
|
|
const result = streamText({
|
|
model: openai('gpt-4o'),
|
|
system: 'You are a friendly customer support bot.',
|
|
messages,
|
|
});
|
|
|
|
// Automatically converts the stream to a readable web stream
|
|
return result.toDataStreamResponse();
|
|
}
|
|
```
|
|
|
|
### Structured Data (JSON) Generation
|
|
|
|
```typescript
|
|
import { generateObject } from 'ai';
|
|
import { openai } from '@ai-sdk/openai';
|
|
import { z } from 'zod';
|
|
|
|
const { object } = await generateObject({
|
|
model: openai('gpt-4o-2024-08-06'), // Use models good at structured output
|
|
system: 'Extract information from the receipt text.',
|
|
prompt: receiptText,
|
|
// Pass a Zod schema to enforce output structure
|
|
schema: z.object({
|
|
storeName: z.string(),
|
|
totalAmount: z.number(),
|
|
items: z.array(z.object({
|
|
name: z.string(),
|
|
price: z.number(),
|
|
})),
|
|
date: z.string().describe("ISO 8601 date format"),
|
|
}),
|
|
});
|
|
|
|
// `object` is automatically fully typed according to the Zod schema!
|
|
console.log(object.totalAmount);
|
|
```
|
|
|
|
## Frontend UI Hooks
|
|
|
|
### `useChat` (Conversational UI)
|
|
|
|
```tsx
|
|
// app/page.tsx (Next.js Client Component)
|
|
"use client";
|
|
|
|
import { useChat } from "ai/react";
|
|
|
|
export default function Chat() {
|
|
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
|
|
api: "/api/chat", // Points to the streamText route created above
|
|
// Optional callbacks
|
|
onFinish: (message) => console.log("Done streaming:", message),
|
|
onError: (error) => console.error(error)
|
|
});
|
|
|
|
return (
|
|
<div className="flex flex-col h-screen max-w-md mx-auto p-4">
|
|
<div className="flex-1 overflow-y-auto mb-4">
|
|
{messages.map((m) => (
|
|
<div key={m.id} className={`mb-4 ${m.role === 'user' ? 'text-right' : 'text-left'}`}>
|
|
<span className={`p-2 rounded-lg inline-block ${m.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}>
|
|
{m.target || m.content}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="flex gap-2">
|
|
<input
|
|
value={input}
|
|
onChange={handleInputChange}
|
|
placeholder="Say something..."
|
|
className="flex-1 p-2 border rounded"
|
|
disabled={isLoading}
|
|
/>
|
|
<button type="submit" disabled={isLoading} className="bg-black text-white p-2 rounded">
|
|
Send
|
|
</button>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Tool Calling (Function Calling)
|
|
|
|
Tools allow the LLM to interact with your code, fetching external data or performing actions before responding to the user.
|
|
|
|
### Server-Side Tool Definition
|
|
|
|
```typescript
|
|
// app/api/chat/route.ts
|
|
import { streamText, tool } from 'ai';
|
|
import { openai } from '@ai-sdk/openai';
|
|
import { z } from 'zod';
|
|
|
|
export async function POST(req: Request) {
|
|
const { messages } = await req.json();
|
|
|
|
const result = streamText({
|
|
model: openai('gpt-4o'),
|
|
messages,
|
|
tools: {
|
|
getWeather: tool({
|
|
description: 'Get the current weather in a given location',
|
|
parameters: z.object({
|
|
location: z.string().describe('The city and state, e.g. San Francisco, CA'),
|
|
unit: z.enum(['celsius', 'fahrenheit']).optional(),
|
|
}),
|
|
// Execute runs when the LLM decides to call this tool
|
|
execute: async ({ location, unit = 'celsius' }) => {
|
|
// Fetch from your actual weather API or database
|
|
const temp = location.includes("San Francisco") ? 15 : 22;
|
|
return `The weather in ${location} is ${temp}° ${unit}.`;
|
|
},
|
|
}),
|
|
},
|
|
// Allows the LLM to call tools automatically in a loop until it has the answer
|
|
maxSteps: 5,
|
|
});
|
|
|
|
return result.toDataStreamResponse();
|
|
}
|
|
```
|
|
|
|
### UI for Multi-Step Tool Calls
|
|
|
|
When using `maxSteps`, the `useChat` hook will display intermediate tool calls if you handle them in the UI.
|
|
|
|
```tsx
|
|
// Inside the `useChat` messages.map loop
|
|
{m.role === 'assistant' && m.toolInvocations?.map((toolInvocation) => (
|
|
<div key={toolInvocation.toolCallId} className="text-sm text-gray-500">
|
|
{toolInvocation.state === 'result' ? (
|
|
<p>✅ Fetched weather for {toolInvocation.args.location}</p>
|
|
) : (
|
|
<p>⏳ Fetching weather for {toolInvocation.args.location}...</p>
|
|
)}
|
|
</div>
|
|
))}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
- ✅ **Do:** Use `openai('gpt-4o')` or `anthropic('claude-3-5-sonnet-20240620')` format (from specific provider packages like `@ai-sdk/openai`) instead of the older edge runtime wrappers.
|
|
- ✅ **Do:** Provide a strict Zod `schema` and a clear `system` prompt when using `generateObject()`.
|
|
- ✅ **Do:** Set `maxDuration = 30` (or higher if on Pro) in Next.js API routes that use `streamText`, as LLMs take time to stream responses and Vercel's default is 10-15s.
|
|
- ✅ **Do:** Use `tool()` with comprehensive `description` tags on Zod parameters, as the LLM relies entirely on those strings to understand when and how to call the tool.
|
|
- ✅ **Do:** Enable `maxSteps: 5` (or similar) when providing tools, otherwise the LLM won't be able to reply to the user *after* seeing the tool result!
|
|
- ❌ **Don't:** Forget to return `result.toDataStreamResponse()` in Next.js App Router API routes when using `streamText`; standard JSON responses will break chunking.
|
|
- ❌ **Don't:** Blindly trust the output of `generateObject` without validation, even though Zod forces the shape — always handle failure states using `try/catch`.
|
|
|
|
## Troubleshooting
|
|
|
|
**Problem:** The streaming chat cuts off abruptly after 10-15 seconds.
|
|
**Solution:** The serverless function timed out. Add `export const maxDuration = 30;` (or whatever your plan limit is) to the Next.js API route file.
|
|
|
|
**Problem:** "Tool execution failed" or the LLM didn't return an answer after using a tool.
|
|
**Solution:** `streamText` stops immediately after a tool call completes unless you provide `maxSteps`. Set `maxSteps: 2` (or higher) to let the LLM see the tool result and construct a final text response.
|
|
|
|
## Limitations
|
|
- Use this skill only when the task clearly matches the scope described above.
|
|
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
|
|
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
|