vercel-ai-sdk

📁 dsantiagomj/dsmj-ai-toolkit 📅 5 days ago
4
总安装量
4
周安装量
#49111
全站排名
安装命令
npx skills add https://github.com/dsantiagomj/dsmj-ai-toolkit --skill vercel-ai-sdk

Agent 安装分布

gemini-cli 4
github-copilot 4
codex 4
amp 4
kimi-cli 4
cursor 4

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


External References


Maintained by dsmj-ai-toolkit