Documentation

Patterns Overview

Learn how AI SDK patterns provide complete, production-ready AI applications.

Complete AI applications and patterns built on top of the Vercel AI SDK, shadcn/ui, and AI Elements. Copy full-stack components for chat, agents, and tool calling.

What are Patterns?

Patterns are complete, production-ready AI applications that you can copy and customize:

  • Full-stack AI agents with tool calling and multi-step workflows
  • AI tools and utilities for common tasks
  • AI patterns for research, analysis, and content generation
  • AI artifacts like dynamic tables, charts, and forms
  • Marketing AI components for landing pages and user acquisition
  • Problem-solving components for common AI challenges
  • Web scraping and data processing tools for content extraction
  • Human-in-the-loop workflows for safe AI interactions

Pattern Categories

AI Agent Patterns

Sophisticated agent implementations with advanced workflows:

AI Artifact Patterns

Interactive AI-generated content and data visualization:

AI Elements Patterns

Basic AI interaction patterns using AI Elements:

AI SDK Integration Patterns

Specific AI SDK features and provider integrations:

Web Search & Scraping Tools

Powerful tools for web content extraction and search:

Marketing & UI Components

Components for marketing and user interface:

AI Gateway & Rate Limiting

Vercel AI Gateway

Many patterns integrate with Vercel's AI Gateway for enhanced performance and reliability:

  • Unified API: Single endpoint for multiple AI providers
  • Automatic retries: Built-in retry logic for failed requests
  • Request deduplication: Prevents duplicate requests
  • Analytics: Detailed usage analytics and monitoring
  • Cost optimization: Automatic provider selection based on cost

Upstash Rate Limiting

All production patterns include rate limiting using Upstash Redis:

import { Ratelimit } from "@upstash/ratelimit"
import { Redis } from "@upstash/redis"
 
// Create a new ratelimiter
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"),
  analytics: true,
  prefix: "@upstash/ratelimit",
})
 
export async function checkRateLimit(identifier: string) {
  const { success, limit, reset, remaining } = await ratelimit.limit(identifier)
 
  return {
    success,
    limit,
    reset,
    remaining,
    headers: {
      "X-RateLimit-Limit": limit.toString(),
      "X-RateLimit-Remaining": remaining.toString(),
      "X-RateLimit-Reset": reset.toString(),
    },
  }
}

Rate Limiting Configuration

Different patterns use different rate limiting strategies:

  • AI Agents: 200 requests per day per IP
  • Web Search Tools: 10 requests per minute
  • Artifact Generation: 100 requests per day per IP
  • Human-in-the-Loop: 50 requests per hour per user

Using Patterns

1. Browse Available Patterns

Visit the patterns page to see all available patterns organized by category.

2. Copy Pattern Code

Click any pattern to view its implementation and copy the code directly into your project.

3. Install Dependencies

Most patterns require the AI SDK and provider packages:

pnpm add ai @ai-sdk/react @ai-sdk/openai @ai-sdk/google @upstash/ratelimit @upstash/redis

4. Set Environment Variables

Configure your AI provider API keys and rate limiting:

# .env.local
OPENAI_API_KEY=your_openai_api_key
GOOGLE_API_KEY=your_google_api_key
CLAUDE_API_KEY=your_claude_api_key
 
# Required for rate limiting (recommended for production)
UPSTASH_REDIS_REST_URL=your_redis_url
UPSTASH_REDIS_REST_TOKEN=your_redis_token
 
# Optional: Vercel AI Gateway
VERCEL_AI_GATEWAY_URL=your_gateway_url

5. Customize

Modify the pattern to fit your specific needs:

export function MyCustomAgent() {
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({ api: "/api/my-custom-agent" }),
    messages: [
      {
        id: "1",
        role: "assistant",
        parts: [
          {
            type: "text",
            text: "Hello! I'm your custom AI agent. How can I help?",
          },
        ],
      },
    ],
  })
 
  return (
    <div className="mx-auto max-w-4xl">
      {/* Your customized implementation */}
    </div>
  )
}

Pattern Structure

Each pattern follows a consistent structure for easy integration:

pattern-name/
├── app/
│   ├── layout.tsx          # Pattern layout wrapper
│   ├── page.tsx           # Main page component
│   └── api/               # API routes for AI functionality
│       └── route-name/
│           └── route.ts   # API endpoint implementation
├── components/            # React components
│   ├── demo-name.tsx     # Main demo component
│   └── ...               # Additional UI components
├── lib/                   # Utilities and configurations
│   ├── rate-limit.ts     # Rate limiting implementation
│   ├── request-utils.ts  # Request validation utilities
│   └── ...               # AI tools and schemas
├── hooks/                 # Custom React hooks (optional)
├── README.md             # Comprehensive documentation
└── package.json          # Dependencies and metadata

Key Files Explained

  • app/page.tsx: Main page component that renders the demo
  • app/layout.tsx: Layout wrapper for consistent styling
  • app/api/*/route.ts: API endpoints handling AI functionality with rate limiting
  • components/*.tsx: React components following clean architecture patterns
  • lib/rate-limit.ts: Upstash Redis rate limiting implementation
  • lib/request-utils.ts: Request validation and error handling utilities
  • README.md: Detailed documentation with setup instructions and examples

AI Agent Patterns

Sophisticated agent implementations with advanced workflows:

Web Search & Scraping Tools

Powerful tools for web content extraction and search:

AI Artifacts

Interactive AI-generated content and data visualization:

Best Practices

AI SDK Integration

  • Use streaming: Always prefer streaming responses for better UX
  • Handle errors: Implement proper error handling and fallbacks
  • Type safety: Use TypeScript for better development experience
  • Rate limiting: Implement rate limiting for production use
  • Tool validation: Use Zod schemas for input validation
  • Empty states: Always include empty state components when no data is present

Pattern Development

  • Follow conventions: Use established conventions from existing patterns
  • Document everything: Provide clear documentation and examples
  • Test thoroughly: Ensure patterns work across different scenarios
  • Optimize performance: Consider bundle size and runtime performance
  • Modular architecture: Keep components focused on single responsibilities
  • Custom hooks: Use custom hooks for complex state logic

Security

  • API key protection: Never expose API keys in client-side code
  • Input validation: Validate all user inputs with Zod schemas
  • Rate limiting: Prevent abuse with proper rate limiting
  • Content filtering: Implement content moderation when needed
  • Error boundaries: Use React error boundaries for graceful failures

Production Considerations

  • Environment variables: Use proper environment variable management
  • Monitoring: Implement logging and monitoring for production
  • Caching: Use appropriate caching strategies for API calls
  • Database connections: Manage database connections efficiently
  • Memory management: Avoid memory leaks in long-running processes

Common Patterns

API Route Pattern

// app/api/example/route.ts
import { openai } from "@ai-sdk/openai"
import { generateText } from "ai"
 
import { checkRateLimit } from "@/lib/rate-limit"
 
export async function POST(req: Request) {
  try {
    const { prompt } = await req.json()
 
    // Rate limiting check
    const identifier = req.headers.get("x-forwarded-for") ?? "unknown"
    const { success } = await checkRateLimit(identifier)
 
    if (!success) {
      return new Response("Rate limit exceeded", { status: 429 })
    }
 
    // AI SDK integration
    const result = await generateText({
      model: openai("gpt-4"),
      prompt: prompt,
    })
 
    return Response.json({ result: result.text })
  } catch (error) {
    console.error("API Error:", error)
    return new Response("Internal Server Error", { status: 500 })
  }
}

Component Pattern

// components/example-demo.tsx
"use client"
 
import { DefaultChatTransport } from "ai"
import { useChat } from "@ai-sdk/react"
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
 
export function ExampleDemo() {
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({ api: "/api/example" }),
  })
 
  if (messages.length === 0) {
    return (
      <Empty className="mx-auto mt-6 max-w-2xl">
        <EmptyHeader>
          <EmptyMedia variant="icon">
            <div className="flex size-12 items-center justify-center rounded-full border bg-white dark:bg-card p-1 shadow-sm">
              <Icon className="size-6" />
            </div>
          </EmptyMedia>
          <EmptyTitle>Example Demo</EmptyTitle>
          <EmptyDescription>
            Description of the component with{" "}
            <span className="border-border bg-muted rounded-sm border-[1px] px-1 py-0.5 font-mono font-light tracking-tight">
              ComponentName
            </span>{" "}
            highlighted.
          </EmptyDescription>
        </EmptyHeader>
        <EmptyContent>
          <div className="space-y-4">
            <div>
              <p className="text-muted-foreground mb-3 text-sm">
                Usage instructions and helpful context.
              </p>
            </div>
            <div className="space-y-2">
              <p className="text-sm font-medium">Example prompts:</p>
              <ul className="text-muted-foreground space-y-1 text-sm">
                <li>• "Example prompt 1"</li>
                <li>• "Example prompt 2"</li>
              </ul>
            </div>
          </div>
        </EmptyContent>
      </Empty>
    )
  }
 
  return (
    <div className="mx-auto max-w-4xl">
      {/* Chat interface implementation */}
    </div>
  )
}

Tool Definition Pattern

// lib/tools.ts
import { tool } from "ai"
import { z } from "zod"
 
export const exampleTool = tool({
  description: "Tool description for AI understanding",
  inputSchema: z.object({
    parameter: z.string().describe("Parameter description"),
    optional: z.string().optional().describe("Optional parameter"),
  }),
  execute: async ({ parameter, optional }) => {
    // Tool implementation
    return {
      result: "Tool execution result",
      metadata: {
        processedAt: new Date().toISOString(),
        parameter,
      },
    }
  },
})

Examples

Simple Chat Pattern

"use client"
 
import { DefaultChatTransport } from "ai"
import { useChat } from "@ai-sdk/react"
 
import {
  Conversation,
  ConversationContent,
  Message,
  MessageContent,
  PromptInput,
  PromptInputBody,
  PromptInputSubmit,
  PromptInputTextarea,
} from "@/components/ai-elements"
 
export function SimpleChatBlock() {
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({ api: "/api/chat" }),
  })
 
  return (
    <div className="flex h-screen flex-col">
      <Conversation>
        <ConversationContent>
          {messages.map((message) => (
            <Message key={message.id} from={message.role}>
              <MessageContent>
                {message.parts.map((part, index) =>
                  part.type === "text" ? (
                    <span key={index}>{part.text}</span>
                  ) : null
                )}
              </MessageContent>
            </Message>
          ))}
        </ConversationContent>
      </Conversation>
 
      <PromptInput
        onSubmit={(message) => {
          if (message.text.trim()) {
            sendMessage({ text: message.text })
          }
        }}
      >
        <PromptInputBody>
          <PromptInputTextarea placeholder="Type your message..." />
        </PromptInputBody>
        <PromptInputSubmit status={status} />
      </PromptInput>
    </div>
  )
}

Tool Calling Pattern

Tools are defined server-side. The client renders tool calls and results as typed tool parts on each message:

// app/api/weather-chat/route.ts
import { streamText, tool, toUIMessageStream, createUIMessageStreamResponse } from "ai"
import { z } from "zod"
 
export async function POST(req: Request) {
  const { messages } = await req.json()
 
  const result = streamText({
    model: "openai/gpt-5.4-nano",
    messages,
    tools: {
      getWeather: tool({
        description: "Get the current weather for a location",
        inputSchema: z.object({
          location: z.string().describe("The city and state"),
        }),
        execute: async ({ location }) => ({
          location,
          temperature: "72°F",
          condition: "Sunny",
        }),
      }),
    },
  })
 
  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  })
}
// components/weather-tool-block.tsx
"use client"
 
import { DefaultChatTransport } from "ai"
import { useChat } from "@ai-sdk/react"
 
export function WeatherToolBlock() {
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({ api: "/api/weather-chat" }),
  })
 
  return (
    <div>
      {messages.map((message) => (
        <div key={message.id}>
          {message.parts.map((part, index) => {
            if (part.type === "text") {
              return <p key={index}>{part.text}</p>
            }
            if (part.type === "tool-getWeather" && part.state === "output-available") {
              return (
                <p key={index}>
                  {part.output.location}: {part.output.temperature}
                </p>
              )
            }
            return null
          })}
        </div>
      ))}
    </div>
  )
}

Human-in-the-Loop Pattern

Require user approval before a server-side tool runs by setting toolApproval on streamText (or your agent). The tool part enters the approval-requested state on the client, where the user approves or denies it with addToolApprovalResponse:

// app/api/human-in-loop/route.ts (excerpt)
const result = streamText({
  model: "openai/gpt-5.4-nano",
  messages,
  tools: { getWeather },
  toolApproval: {
    getWeather: "user-approval",
  },
})
// components/human-in-loop-block.tsx
"use client"
 
import { useChat } from "@ai-sdk/react"
 
import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
 
export function HumanInLoopBlock() {
  const { messages, addToolApprovalResponse } = useChat()
 
  return (
    <div className="mx-auto max-w-4xl space-y-4">
      {messages.map((message) => (
        <div key={message.id}>
          {message.parts.map((part, index) => {
            if (part.type === "text") {
              return <p key={index}>{part.text}</p>
            }
 
            if (
              part.type === "tool-getWeather" &&
              part.state === "approval-requested"
            ) {
              return (
                <Card key={part.toolCallId}>
                  <CardHeader>
                    <CardTitle>Tool Approval Required</CardTitle>
                    <CardDescription>
                      The AI wants to check the weather.
                    </CardDescription>
                  </CardHeader>
                  <CardContent>
                    <pre className="bg-muted rounded p-2 text-sm">
                      {JSON.stringify(part.input, null, 2)}
                    </pre>
                    <div className="mt-2 flex gap-2">
                      <Button
                        onClick={() =>
                          addToolApprovalResponse({
                            id: part.approval.id,
                            approved: true,
                          })
                        }
                      >
                        Approve
                      </Button>
                      <Button
                        variant="outline"
                        onClick={() =>
                          addToolApprovalResponse({
                            id: part.approval.id,
                            approved: false,
                          })
                        }
                      >
                        Reject
                      </Button>
                    </div>
                  </CardContent>
                </Card>
              )
            }
 
            return null
          })}
        </div>
      ))}
    </div>
  )
}

Deployment & Production

Environment Setup

  1. Set up Upstash Redis for rate limiting:

    # Install Upstash CLI
    npm install -g @upstash/cli
     
    # Create Redis database
    upstash redis create my-redis-db
  2. Configure environment variables:

    # .env.local
    OPENAI_API_KEY=your_openai_api_key
    GOOGLE_API_KEY=your_google_api_key
    CLAUDE_API_KEY=your_claude_api_key
     
    # Upstash Redis
    UPSTASH_REDIS_REST_URL=https://your-redis-url.upstash.io
    UPSTASH_REDIS_REST_TOKEN=your_redis_token
     
    # Optional: Vercel AI Gateway
    VERCEL_AI_GATEWAY_URL=https://your-gateway-url.vercel.app
  3. Deploy to Vercel:

    # Install Vercel CLI
    npm install -g vercel
     
    # Deploy
    vercel --prod

Monitoring & Analytics

  • Vercel Analytics: Built-in performance monitoring
  • Upstash Console: Redis usage and rate limiting metrics
  • AI Gateway Analytics: AI provider usage and costs
  • Custom Logging: Implement structured logging for debugging

Cost Optimization

  • Rate limiting: Prevent abuse and control costs
  • Caching: Cache responses to reduce API calls
  • Provider selection: Use cost-effective models when appropriate
  • Monitoring: Track usage patterns and optimize accordingly

Troubleshooting

Common Issues

  1. Rate limit exceeded: Check Upstash Redis configuration
  2. API key errors: Verify environment variables are set correctly
  3. Tool execution failures: Check tool schemas and error handling
  4. Streaming issues: Ensure proper error boundaries and fallbacks

Debug Tips

  • Use browser dev tools to inspect network requests
  • Check server logs for API errors
  • Verify rate limiting headers in responses
  • Test with different input scenarios

Next Steps