vercel-ai-sdk

Vercel AI SDK - Build AI-Powered Apps

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "vercel-ai-sdk" with this command: npx skills add dsantiagomj/dsmj-ai-toolkit/dsantiagomj-dsmj-ai-toolkit-vercel-ai-sdk

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 &#x26;&#x26; &#x3C;div className="loading">AI is thinking...&#x3C;/div>}

    {error &#x26;&#x26; (
      &#x3C;div className="error">
        &#x3C;p>Error: {error.message}&#x3C;/p>
        &#x3C;button onClick={() => reload()}>Retry&#x3C;/button>
      &#x3C;/div>
    )}
  &#x3C;/div>

  &#x3C;form onSubmit={handleSubmit}>
    &#x3C;input
      value={input}
      onChange={handleInputChange}
      disabled={isLoading}
      placeholder="Type a message..."
    />
    &#x3C;button type="submit" disabled={isLoading || !input.trim()}>
      Send
    &#x3C;/button>
  &#x3C;/form>
&#x3C;/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) => (
        &#x3C;div key={i} className="tool-call">
          {tool.state === 'call' &#x26;&#x26; (
            &#x3C;p>Calling {tool.toolName}...&#x3C;/p>
          )}
          {tool.state === 'result' &#x26;&#x26; (
            &#x3C;div>
              &#x3C;p>Used {tool.toolName}&#x3C;/p>
              &#x3C;pre>{JSON.stringify(tool.result, null, 2)}&#x3C;/pre>
            &#x3C;/div>
          )}
        &#x3C;/div>
      ))}
    &#x3C;/div>
  ))}
&#x3C;/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

  • Vercel AI SDK Documentation

  • Vercel AI SDK GitHub

Maintained by dsmj-ai-toolkit

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

performance

No summary provided by upstream source.

Repository SourceNeeds Review
General

react

No summary provided by upstream source.

Repository SourceNeeds Review
General

zustand

No summary provided by upstream source.

Repository SourceNeeds Review