Skills api-ai-vercel-ai-sdk
Vercel AI SDK patterns - providers, text generation, streaming, structured output, tool calling, chat UI hooks, embeddings, and RAG
git clone https://github.com/agents-inc/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/agents-inc/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/dist/plugins/api-ai-vercel-ai-sdk/skills/api-ai-vercel-ai-sdk" ~/.claude/skills/agents-inc-skills-api-ai-vercel-ai-sdk && rm -rf "$T"
dist/plugins/api-ai-vercel-ai-sdk/skills/api-ai-vercel-ai-sdk/SKILL.mdVercel AI SDK Patterns
Quick Guide: Use Vercel AI SDK (v6) to build AI-powered applications with a unified provider API. Use
/generateTextfor text generation and streaming,streamText/Output.object()for structured data with Zod,Output.array()for function calling, andtool()/useChathooks for React chat UIs. Supports OpenAI, Anthropic, Google, and 20+ providers through a single API.useCompletion
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use the
package (v6) with ai
/ Output.object()
for structured output -- NOT the deprecated Output.array()
/ generateObject
functions)streamObject
(You MUST define tool input schemas with
and use z.object()
on each property to help the model understand expected inputs).describe()
(You MUST use
for user-facing responses to enable progressive rendering -- use streamText
only for background/non-interactive tasks)generateText
(You MUST handle streaming errors via
callback -- streamText errors become part of the stream and are NOT thrown)onError
(You MUST use
(not inputSchema
) when defining tools -- parameters
was renamed in SDK v5+)parameters
</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 Node.js/React/Next.js app
Key patterns covered:
- Provider setup and model configuration (OpenAI, Anthropic, Google, custom)
- Text generation (
) and streaming (generateText
)streamText - Structured output with Zod schemas (
,Output.object
,Output.array
)Output.choice - Tool calling with
, multi-step execution, and approval flowstool() - React hooks:
for chat UIs,useChat
for text completionuseCompletion - Embeddings (
,embed
) and RAG patterns withembedManycosineSimilarity
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.,
npm package) is simpleropenai - Image generation only (AI SDK supports it, but dedicated image SDKs may be more feature-rich)
Detailed Resources:
- For provider setup, text generation, and error handling, see examples/core.md
- For chat UI patterns with useChat, see examples/chat.md
- For tool definitions and multi-step calling, see examples/tools.md
- For Zod-based structured output, see examples/structured-output.md
- For embeddings and RAG, see examples/rag.md
- For quick reference tables, see reference.md
<philosophy>
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:
- Provider agnostic -- Switch models by changing a string, not rewriting code. The provider abstraction means
andgenerateText({ model: 'openai/gpt-4o' })
use the same API.generateText({ model: 'anthropic/claude-sonnet-4.5' }) - Streaming first --
starts delivering tokens immediately. Use it for all user-facing responses.streamText
blocks until completion and is better for background tasks and agent loops.generateText - Type-safe structured output -- Define Zod schemas and get validated, typed objects back from the model. Use
on schema properties to guide the model..describe() - 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.
- Framework-agnostic UI hooks --
anduseChat
work with React, Svelte, Vue, and Angular. They manage streaming state, message history, and input handling.useCompletion
When to use Vercel AI SDK:
- Multi-provider applications where you want to switch models easily
- Streaming chat interfaces with React/Next.js
- 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)
<patterns>
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.toTextStreamResponse() in route handlers. 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.
</patterns><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>
Integration Guide
Framework support:
- Server-side route handlers for
(any framework with standardstreamText
/Request
)Response - Frontend hooks (
,useChat
) fromuseCompletion
with framework-specific variants for Svelte, Vue, and Angular@ai-sdk/react - Edge runtime compatible (Cloudflare Workers, Vercel Edge)
Provider architecture:
- Core
package providesai
,generateText
,streamText
,embed
,Output
,toolgateway - Provider packages (
,@ai-sdk/openai
,@ai-sdk/anthropic
) auto-read environment variables@ai-sdk/google
supports any OpenAI-compatible API (Ollama, Together AI, etc.)@ai-sdk/openai-compatible- AI Gateway (
) routes to any provider with agateway
stringprovider/model
Schema integration:
- Structured output (
) and tool input schemas use Zod for validation and type inferenceOutput.object() - MCP (Model Context Protocol) integration for standardized tool access
<red_flags>
RED FLAGS
High Priority Issues:
- Using deprecated
/generateObject
instead ofstreamObject
+generateText
(removed in v6)Output.object() - Using
instead ofparameters
in tool definitions (renamed in v5+)inputSchema - Using
for user-facing chat responses (blocks until complete, no streaming)generateText - Hardcoding API keys in source code instead of using environment variables
- Using
instead ofimport { useChat } from 'ai/react'import { useChat } from '@ai-sdk/react' - Using
type instead ofCoreMessage
(renamed in v6)ModelMessage - Calling
instead ofsendMessage({ role: 'user', content: text })
(v6 API change)sendMessage({ text })
Medium Priority Issues:
- Missing
on Zod schema properties for structured output (model gets less guidance).describe() - Not setting
withstopWhen
for multi-step tool calling (risks infinite loops)stepCountIs() - Not handling stream errors with
callback (errors silently disappear)onError - Using
instead ofsystem
ininstructions
(renamed in v6)ToolLoopAgent
Common Mistakes:
- Forgetting that
does NOT throw errors -- they appear in the stream as error eventsstreamText - Not consuming the stream from
-- the function returns immediately, you must iterate the streamstreamText - Using
destructure from deprecatedobject
instead ofgenerateObject
fromoutput
withgenerateTextOutput.object() - Passing raw strings to
parameter without a provider prefix (e.g.,model
instead of'gpt-4o'
)'openai/gpt-4o'
Gotchas & Edge Cases:
transform adds slight delay but makes output feel more natural -- always use for chat UIssmoothStream()
withOutput.array()
yields each element only when fully validated -- partial elements are not emittedelementStream
andembed()
require embedding model strings (e.g.,embedMany()
), not chat model strings'openai/text-embedding-3-small'- Zod schema support varies by provider -- complex unions and transforms may not work with all models
v6 no longer manages input state -- you must use externaluseChat
for the input field and calluseState
(notsendMessage({ text })
){ role, content }
is async in v6 (was sync asconvertToModelMessages()
in v5)convertToCoreMessages()
gives you all event types includingfullStream
,tool-call
,tool-result
, andsource
--error
only gives text deltastextStream- Token usage is available via
property on results, including cache hit details inusageusage.inputTokenDetails
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use the
package (v6) with ai
/ Output.object()
for structured output -- NOT the deprecated Output.array()
/ generateObject
functions)streamObject
(You MUST define tool input schemas with
and use z.object()
on each property to help the model understand expected inputs).describe()
(You MUST use
for user-facing responses to enable progressive rendering -- use streamText
only for background/non-interactive tasks)generateText
(You MUST handle streaming errors via
callback -- streamText errors become part of the stream and are NOT thrown)onError
(You MUST use
(not inputSchema
) when defining tools -- parameters
was renamed in SDK v5+)parameters
Failure to follow these rules will produce broken AI integrations, deprecated API usage, or poor user experiences with blocked responses.
</critical_reminders>