ai-orchestration-vercel-ai-sdk

Installation
SKILL.md

Vercel AI SDK Patterns

Quick Guide: Use Vercel AI SDK (v6) to build AI-powered applications with a unified provider API. Use generateText/streamText for text generation and streaming, Output.object()/Output.array() for structured data with Zod, tool() for function calling, and useChat/useCompletion hooks for React chat UIs. Supports OpenAI, Anthropic, Google, and 20+ providers through a single API.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use the ai package (v6) with Output.object() / Output.array() for structured output -- NOT the deprecated generateObject / streamObject functions)

(You MUST define tool input schemas with z.object() and use .describe() on each property to help the model understand expected inputs)

(You MUST use streamText for user-facing responses to enable progressive rendering -- use generateText only for background/non-interactive tasks)

(You MUST handle streaming errors via onError callback -- streamText errors become part of the stream and are NOT thrown)

(You MUST use inputSchema (not parameters) when defining tools -- parameters was renamed in SDK v5+)

</critical_requirements>


Auto-detection: AI SDK, Vercel AI, generateText, streamText, generateObject, streamObject, Output.object, Output.array, useChat, useCompletion, @ai-sdk/openai, @ai-sdk/anthropic, @ai-sdk/google, tool(), toolChoice, embedMany, embed, cosineSimilarity, ToolLoopAgent, smoothStream

When to use:

  • Building AI chat interfaces with streaming responses
  • Generating structured data (JSON objects, arrays) from LLMs with Zod schema validation
  • Implementing tool calling / function calling with LLMs
  • Creating multi-provider AI applications (OpenAI, Anthropic, Google, etc.)
  • Building RAG pipelines with embeddings and vector similarity
  • Adding AI text completion or generation to any TypeScript app

Key patterns covered:

  • Provider setup and model configuration (OpenAI, Anthropic, Google, custom)
  • Text generation (generateText) and streaming (streamText)
  • Structured output with Zod schemas (Output.object, Output.array, Output.choice)
  • Tool calling with tool(), multi-step execution, and approval flows
  • React hooks: useChat for chat UIs, useCompletion for text completion
  • Embeddings (embed, embedMany) and RAG patterns with cosineSimilarity

When NOT to use:

  • Simple static content that doesn't need AI generation
  • Server-side-only batch jobs where a direct provider SDK (e.g., openai npm package) is simpler
  • Image generation only (AI SDK supports it, but dedicated image SDKs may be more feature-rich)

Detailed Resources:


Philosophy

The Vercel AI SDK provides a unified TypeScript API for building AI-powered applications across providers. Instead of learning each provider's unique SDK, you write one set of code that works with OpenAI, Anthropic, Google, and 20+ other providers.

Core principles:

  1. Provider agnostic -- Switch models by changing a string, not rewriting code. The provider abstraction means generateText({ model: 'openai/gpt-4o' }) and generateText({ model: 'anthropic/claude-sonnet-4.5' }) use the same API.
  2. Streaming first -- streamText starts delivering tokens immediately. Use it for all user-facing responses. generateText blocks until completion and is better for background tasks and agent loops.
  3. Type-safe structured output -- Define Zod schemas and get validated, typed objects back from the model. Use .describe() on schema properties to guide the model.
  4. Tools as first-class citizens -- Define tools with Zod input schemas and execute functions. The SDK handles the tool call loop, including multi-step execution and human approval.
  5. Framework-agnostic UI hooks -- useChat and useCompletion work with React, Svelte, Vue, and Angular. They manage streaming state, message history, and input handling.

When to use Vercel AI SDK:

  • Multi-provider applications where you want to switch models easily
  • Streaming chat interfaces with React (or Svelte, Vue, Angular)
  • Structured data extraction from natural language
  • Agent-style applications with tool calling loops
  • RAG systems with embedding and retrieval

When NOT to use:

  • Single-provider scripts where the native SDK is simpler and has fewer dependencies
  • Extremely high-throughput batch processing (direct API calls avoid SDK overhead)
  • Non-TypeScript environments (the SDK is TypeScript-first)

Core Patterns

Pattern 1: Provider Setup

Configure providers via direct imports (auto-reads env vars), custom instances, or AI Gateway. See examples/core.md for full examples.

import { gateway } from "ai";
import { openai } from "@ai-sdk/openai";

// Gateway: provider/model string routing
const model = gateway("anthropic/claude-sonnet-4.5");

// Direct: auto-reads OPENAI_API_KEY from env
const openaiModel = openai("gpt-4o");

Use customProvider for semantic model aliases (models('fast'), models('smart')). Never hardcode API keys.


Pattern 2: Text Generation with generateText

Use generateText for non-interactive tasks. Returns a promise that resolves when complete. See examples/core.md.

import { generateText } from "ai";

const { text, usage } = await generateText({
  model: "openai/gpt-4o",
  system: "You are a professional technical writer.",
  prompt: `Summarize: ${article}`,
});

Use ModelMessage[] for multi-turn conversations. Append response.messages for continued dialogue. Do NOT use generateText for user-facing responses -- use streamText instead.


Pattern 3: Streaming with streamText

Use streamText for all user-facing responses. Errors are part of the stream (not thrown) -- use onError. See examples/core.md.

import { streamText, smoothStream } from "ai";

const result = streamText({
  model: "anthropic/claude-sonnet-4.5",
  prompt: "Explain TypeScript.",
  experimental_transform: smoothStream(),
  onError({ error }) {
    console.error("Stream error:", error);
  },
});

for await (const part of result.textStream) {
  process.stdout.write(part);
}

Use result.toUIMessageStreamResponse() in route handlers serving useChat, or result.toTextStreamResponse() for plain text streaming. Use result.fullStream for granular event types (text-delta, tool-call, error, finish).


Pattern 4: Structured Output with Zod

Use Output.object() with generateText/streamText for type-safe structured data. See examples/structured-output.md.

import { generateText, Output } from "ai";
import { z } from "zod";

const schema = z.object({
  name: z.string().describe("Recipe name"),
  steps: z.array(z.string()).describe("Cooking instructions"),
});

const { output } = await generateText({
  model: "openai/gpt-4o",
  output: Output.object({ schema }),
  prompt: "Generate a vegetarian lasagna recipe.",
});

Key variants: Output.array({ element }) with elementStream for streaming arrays, Output.choice() for classification, partialOutputStream for streaming partial objects. Do NOT use deprecated generateObject/streamObject.


Pattern 5: Tool Calling

Define tools with tool(), Zod inputSchema, and execute. The SDK handles multi-step loops. See examples/tools.md.

import { generateText, tool, stepCountIs } from "ai";
import { z } from "zod";

const weatherTool = tool({
  description: "Get weather in a location",
  inputSchema: z.object({
    location: z.string().describe("City name"),
  }),
  execute: async ({ location }) => fetchWeather(location),
});

const MAX_STEPS = 5;
const { text } = await generateText({
  model: "openai/gpt-4o",
  tools: { weather: weatherTool },
  stopWhen: stepCountIs(MAX_STEPS),
  prompt: "Weather in SF and Tokyo?",
});

Key features: needsApproval for human-in-the-loop, ToolLoopAgent for reusable agents (use instructions not system), toolChoice to force/prevent tool usage, activeTools/prepareStep for per-step control. Always use stepCountIs() to prevent infinite loops.


Pattern 6: useChat Hook (React)

useChat manages streaming chat state. v6 uses transport-based architecture and external input state. See examples/chat.md.

import { useChat } from "@ai-sdk/react";
import { useState } from "react";

export function Chat() {
  const [input, setInput] = useState("");
  const { messages, sendMessage, status, stop, error } = useChat();

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (!input.trim()) return;
    sendMessage({ text: input }); // NOT { role, content }
    setInput("");
  }
  // ... render messages.parts, status-based UI
}

v6 breaking changes: sendMessage({ text }) replaces handleSubmit/append({ role, content }). External useState for input (hook no longer manages it). status replaces isLoading. Import from @ai-sdk/react not ai/react.


Pattern 7: useCompletion Hook (React)

useCompletion handles single-turn text completions. Unlike useChat, it still manages input state internally. See examples/core.md.

import { useCompletion } from "@ai-sdk/react";

const { completion, input, handleInputChange, handleSubmit, isLoading } =
  useCompletion({
    api: "/api/completion",
  });

Good for autocomplete, summarization, and one-shot generation where multi-turn chat is not needed.


<decision_framework>

Decision Framework

Which Function to Use

Do you need AI-generated content?
├─ YES -> Is it user-facing (needs progressive display)?
│   ├─ YES -> Is it a multi-turn conversation?
│   │   ├─ YES -> useChat hook (React) or streamText (server)
│   │   └─ NO -> Is it a single completion/generation?
│   │       ├─ YES -> useCompletion hook (React) or streamText (server)
│   │       └─ NO -> streamText with custom UI
│   └─ NO -> Is it a background task (agent, batch)?
│       ├─ YES -> generateText (blocks until complete)
│       └─ NO -> generateText for simple one-shots
├─ Do you need structured data (JSON/objects)?
│   ├─ YES -> Output.object() with Zod schema
│   │   ├─ Need streaming partial object? -> streamText + partialOutputStream
│   │   ├─ Need array of items? -> Output.array() + elementStream
│   │   └─ Need one of N options? -> Output.choice()
│   └─ NO -> Plain text generation
├─ Do you need the model to call functions?
│   ├─ YES -> Define tools with tool() + Zod inputSchema
│   │   ├─ Multi-step reasoning? -> stopWhen: stepCountIs(N)
│   │   ├─ Need human approval? -> needsApproval on tool
│   │   └─ Single tool call? -> Default (stops after first response)
│   └─ NO -> No tools needed
└─ Do you need vector embeddings?
    ├─ Single text -> embed()
    ├─ Batch of texts -> embedMany()
    └─ Similarity search -> cosineSimilarity()

Which Provider to Choose

What is your primary concern?
├─ Best reasoning / complex tasks -> anthropic/claude-sonnet-4.5 or openai/o3
├─ Fast + cheap for simple tasks -> openai/gpt-4o-mini or anthropic/claude-haiku-4.5
├─ Structured output reliability -> openai/gpt-4o (best schema adherence)
├─ Multi-modal (images + text) -> openai/gpt-4o or anthropic/claude-sonnet-4.5
├─ Google ecosystem / grounding -> google/gemini-2.5-flash
└─ Provider agnostic -> Use AI Gateway with model aliases

</decision_framework>


Integration Guide

Framework support:

  • Server-side route handlers for streamText (any framework with standard Request/Response)
  • Frontend hooks (useChat, useCompletion) from @ai-sdk/react with framework-specific variants for Svelte, Vue, and Angular
  • Edge runtime compatible (Cloudflare Workers, Vercel Edge)

Provider architecture:

  • Core ai package provides generateText, streamText, embed, Output, tool, gateway
  • Provider packages (@ai-sdk/openai, @ai-sdk/anthropic, @ai-sdk/google) auto-read environment variables
  • @ai-sdk/openai-compatible supports any OpenAI-compatible API (Ollama, Together AI, etc.)
  • AI Gateway (gateway) routes to any provider with a provider/model string

Schema integration:

  • Structured output (Output.object()) and tool input schemas use Zod for validation and type inference
  • MCP (Model Context Protocol) integration for standardized tool access

<red_flags>

RED FLAGS

High Priority Issues:

  • Using deprecated generateObject / streamObject instead of generateText + Output.object() (removed in v6)
  • Using parameters instead of inputSchema in tool definitions (renamed in v5+)
  • Using generateText for user-facing chat responses (blocks until complete, no streaming)
  • Hardcoding API keys in source code instead of using environment variables
  • Using import { useChat } from 'ai/react' instead of import { useChat } from '@ai-sdk/react'
  • Using CoreMessage type instead of ModelMessage (renamed in v6)
  • Calling sendMessage({ role: 'user', content: text }) instead of sendMessage({ text }) (v6 API change)

Medium Priority Issues:

  • Missing .describe() on Zod schema properties for structured output (model gets less guidance)
  • Not setting stopWhen with stepCountIs() for multi-step tool calling (risks infinite loops)
  • Not handling stream errors with onError callback (errors silently disappear)
  • Using system instead of instructions in ToolLoopAgent (renamed in v6)

Common Mistakes:

  • Forgetting that streamText does NOT throw errors -- they appear in the stream as error events
  • Not consuming the stream from streamText -- the function returns immediately, you must iterate the stream
  • Using object destructure from deprecated generateObject instead of output from generateText with Output.object()
  • Passing raw strings to model parameter without a provider prefix (e.g., 'gpt-4o' instead of 'openai/gpt-4o')

Gotchas & Edge Cases:

  • smoothStream() transform adds slight delay but makes output feel more natural -- always use for chat UIs
  • Output.array() with elementStream yields each element only when fully validated -- partial elements are not emitted
  • embed() and embedMany() require embedding model strings (e.g., 'openai/text-embedding-3-small'), not chat model strings
  • Zod schema support varies by provider -- complex unions and transforms may not work with all models
  • useChat v6 no longer manages input state -- you must use external useState for the input field and call sendMessage({ text }) (not { role, content })
  • convertToModelMessages() is async in v6 (was sync as convertToCoreMessages() in v5) -- converts UIMessage[] to ModelMessage[]
  • fullStream gives you all event types including tool-call, tool-result, source, and error -- textStream only gives text deltas
  • Token usage is available via usage property on results, including cache hit details in usage.inputTokenDetails

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use the ai package (v6) with Output.object() / Output.array() for structured output -- NOT the deprecated generateObject / streamObject functions)

(You MUST define tool input schemas with z.object() and use .describe() on each property to help the model understand expected inputs)

(You MUST use streamText for user-facing responses to enable progressive rendering -- use generateText only for background/non-interactive tasks)

(You MUST handle streaming errors via onError callback -- streamText errors become part of the stream and are NOT thrown)

(You MUST use inputSchema (not parameters) when defining tools -- parameters was renamed in SDK v5+)

Failure to follow these rules will produce broken AI integrations, deprecated API usage, or poor user experiences with blocked responses.

</critical_reminders>

Related skills
Installs
2
GitHub Stars
6
First Seen
Apr 7, 2026