Back to skills
extension
Category: Content & MediaNo API key required

vercel-ai-sdk-development

Use when building AI-powered applications with the Vercel AI SDK (V6+). Covers agents (ToolLoopAgent), tool design with execution approval and strict mode, MCP client integration, structured output with tool calling, streaming patterns, DevTools debugging, reranking, provider-specific tools, and UI integration with React/Next.js.

personAuthor: jakexiaohubgithub

Vercel AI SDK V6 Development

Guidance for building production-grade AI applications using the Vercel AI SDK V6+. This skill covers the agent abstraction, tool ecosystem, MCP integration, structured output, streaming, and UI patterns.

When to Use

  • Building any AI/LLM-powered feature in a TypeScript project
  • Creating agents with multi-step tool calling loops
  • Integrating LLM capabilities into Next.js, React, Svelte, Vue, or Node.js applications
  • Designing tools with human-in-the-loop approval workflows
  • Connecting to MCP servers from application code
  • Generating structured output from LLM calls
  • Implementing RAG pipelines with reranking
  • Debugging AI call chains with DevTools

Core Concepts

Model Gateway

Use the AI SDK gateway for provider-agnostic model references. This decouples your code from specific providers and enables easy model switching:

import { gateway } from 'ai';
const model = gateway('anthropic/claude-sonnet-4.5');

Agents with ToolLoopAgent

Prefer ToolLoopAgent for reusable agent definitions. Define the agent once with its model, instructions, and tools, then use it across API routes, background jobs, and UI streams:

import { ToolLoopAgent } from 'ai';

export const myAgent = new ToolLoopAgent({
  model: 'anthropic/claude-sonnet-4.5',
  instructions: 'You are a helpful assistant.',
  tools: { /* tools here */ },
});

const result = await myAgent.generate({ prompt: '...' });

Key agent patterns:

  • Use callOptionsSchema with prepareCall to inject per-request context (user ID, feature flags, retrieved documents) in a type-safe way.
  • Set stopWhen: stepCountIs(N) to cap the tool loop (default is 20 steps).
  • Export InferAgentUIMessage<typeof agent> for end-to-end type safety from agent definition to UI components.
  • For custom agent behavior, implement the Agent interface directly instead of extending ToolLoopAgent.

Low-Level Functions

Use generateText / streamText when you need full control over a single LLM call without the agent loop, or when composing custom orchestration logic:

import { generateText, streamText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-4.5',
  prompt: '...',
});

Tool Design Best Practices

Structure

Define tools in dedicated files under a tools/ directory. Each tool should have a clear description, a typed inputSchema using Zod, and an execute function:

import { tool } from 'ai';
import { z } from 'zod';

export const weatherTool = tool({
  description: 'Get the current weather for a city',
  inputSchema: z.object({
    city: z.string().describe('City name'),
  }),
  execute: async ({ city }) => {
    // implementation
  },
});

Execution Approval (Human-in-the-Loop)

Set needsApproval: true on tools that perform destructive or sensitive actions (deleting data, processing payments, modifying production resources). Use a function for conditional approval based on input:

export const deleteRecord = tool({
  description: 'Delete a database record',
  inputSchema: z.object({ id: z.string() }),
  needsApproval: true,
  execute: async ({ id }) => { /* ... */ },
});

Strict Mode

Enable strict: true on tools with simple, provider-compatible schemas to guarantee input validation. Leave it off for tools with complex schemas that may not be supported in strict mode by all providers:

export const simpleTool = tool({
  description: '...',
  inputSchema: z.object({ query: z.string() }),
  strict: true,
  execute: async ({ query }) => { /* ... */ },
});

Input Examples

Provide inputExamples for tools with complex or domain-specific input patterns. This helps models generate correctly structured inputs:

export const searchTool = tool({
  description: 'Search documents with filters',
  inputSchema: z.object({
    query: z.string(),
    dateRange: z.string().describe('ISO date range, e.g. 2025-01-01..2025-12-31'),
  }),
  inputExamples: [
    { input: { query: 'quarterly report', dateRange: '2025-01-01..2025-03-31' } },
  ],
  execute: async (input) => { /* ... */ },
});

Custom Model Output with toModelOutput

Use toModelOutput to control what tokens go back to the model, independent of what execute returns. This reduces token usage when tools return large payloads:

export const docSearchTool = tool({
  description: 'Search documents',
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ query }) => {
    return { fullDocument: '...very long content...' };
  },
  toModelOutput: async ({ input, output }) => ({
    type: 'text',
    value: `Found document matching "${input.query}": ${output.fullDocument.slice(0, 200)}...`,
  }),
});

Structured Output with Tool Calling

Combine multi-step tool calling with structured output generation using the output option. Use Output.object(), Output.array(), Output.choice(), Output.json(), or Output.text():

import { Output, ToolLoopAgent } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: 'anthropic/claude-sonnet-4.5',
  tools: { /* ... */ },
  output: Output.object({
    schema: z.object({
      summary: z.string(),
      confidence: z.number(),
      recommendations: z.array(z.string()),
    }),
  }),
});

const { output } = await agent.generate({ prompt: '...' });

MCP Client Integration

Use @ai-sdk/mcp to connect to remote MCP servers and use their tools:

import { createMCPClient } from '@ai-sdk/mcp';

const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://your-server.com/mcp',
    headers: { Authorization: 'Bearer ...' },
  },
});

const tools = await mcpClient.tools();

For servers requiring OAuth, use the auth helper with an OAuthClientProvider to handle PKCE, token refresh, and dynamic client registration automatically.

Access MCP resources and prompts:

const resources = await mcpClient.listResources();
const data = await mcpClient.readResource({ uri: 'file:///path' });

const prompts = await mcpClient.experimental_listPrompts();
const prompt = await mcpClient.experimental_getPrompt({
  name: 'code_review',
  arguments: { code: '...' },
});

Provider-Specific Tools

Leverage provider-native tools when the platform offers optimized capabilities:

  • Anthropic: Memory tool, tool search (regex/BM25), code execution, programmatic tool calling via allowedCallers
  • OpenAI: Shell tool, apply-patch tool, MCP tool
  • Google: Google Maps grounding, Vertex RAG Store, file search
  • xAI: Web search, X search, code execution, image/video analysis

Prefer provider tools over custom implementations when they offer platform-optimized model integration.

Reranking for RAG

Use the rerank function to reorder retrieved documents by relevance before passing them to the model. This reduces noise and improves response quality:

import { rerank } from 'ai';
import { cohere } from '@ai-sdk/cohere';

const { rerankedDocuments } = await rerank({
  model: cohere.reranking('rerank-v3.5'),
  documents,
  query: 'user question',
  topN: 5,
});

Supports both plain-text and structured document reranking. Currently available with Cohere, Amazon Bedrock, and Together.ai.

UI Integration (React / Next.js)

Streaming with useChat

Use useChat on the client with typed messages from your agent definition:

import { useChat } from '@ai-sdk/react';
import type { MyAgentUIMessage } from '@/agents/my-agent';

const { messages, sendMessage } = useChat<MyAgentUIMessage>();

API Route Pattern

Expose agents via API routes using createAgentUIStreamResponse:

import { createAgentUIStreamResponse } from 'ai';
import { myAgent } from '@/agents/my-agent';

export async function POST(request: Request) {
  const { messages } = await request.json();
  return createAgentUIStreamResponse({
    agent: myAgent,
    uiMessages: messages,
  });
}

Tool Approval in UI

Handle needsApproval tools by checking invocation.state === 'approval-requested' and calling addToolApprovalResponse with the approval decision.

DevTools for Debugging

Wrap models with devToolsMiddleware to get full visibility into LLM calls during development:

import { wrapLanguageModel } from 'ai';
import { devToolsMiddleware } from '@ai-sdk/devtools';

const debugModel = wrapLanguageModel({
  model: gateway('anthropic/claude-sonnet-4.5'),
  middleware: devToolsMiddleware(),
});

Run npx @ai-sdk/devtools and open http://localhost:4983 to inspect input/output, token usage, timing, and raw provider payloads. Use this during development to trace multi-step agent behavior.

Project Organization

Recommended file layout for AI SDK projects:

src/
├── agents/        # Agent definitions (one file per agent)
├── tools/         # Tool definitions (one file per tool)
├── api/           # API routes exposing agents
├── components/    # UI components for tool views
└── lib/           # Shared utilities, MCP clients, provider config

Key Rules

  • Always use AI SDK V6+ — Do not use deprecated V4/V5 patterns. Use ToolLoopAgent instead of manual while loops around generateText.
  • Gateway for model references — Use gateway('provider/model') for provider-agnostic code. Avoid hardcoding provider-specific client imports unless using provider tools.
  • Define agents, not inline configs — Create reusable agent files rather than repeating model/tools/instructions at every call site.
  • Tools in dedicated files — Keep tool definitions in tools/ for reuse across agents and type sharing with UI.
  • Approval on destructive actions — Always set needsApproval on tools that modify, delete, or spend resources.
  • Control model output size — Use toModelOutput on tools that return large payloads to avoid wasting context window tokens.
  • Structured output over parsing — Use Output.object() with a Zod schema instead of manually parsing LLM text output.
  • Rerank before stuffing context — When doing RAG, rerank retrieved documents and pass only the top-N to the model.
  • DevTools in development — Always enable devToolsMiddleware during development for visibility into multi-step agent behavior.
  • Consult latest docs — Use Context7 MCP (or equivalent) to verify current AI SDK API surfaces, as the SDK evolves rapidly.