{
    "componentChunkName": "component---src-templates-blog-detail-tsx",
    "path": "/blog/vercel-ai-sdk/",
    "result": {"data":{"site":{"siteMetadata":{"siteTitleShort":"Developer Portfolio"}},"markdownRemark":{"id":"020462f9-328f-51e2-88c9-8caa13baf2ac","excerpt":"A Unified Toolkit for AI in TypeScript A year ago the AI SDK was mostly known for its  hook, the easy way to wire up a streaming chat UI in Next.js. Today it’s…","html":"<h2>A Unified Toolkit for AI in TypeScript</h2>\n<p class=\"lead\">Every model provider ships its own SDK with its own quirks. OpenAI streams differently than Anthropic, Anthropic handles tools differently than Google, and the moment you want to swap providers you end up rewriting half your application. The Vercel AI SDK exists to make that pain go away — one API for every model, with first-class React hooks layered on top.</p>\n<p>A year ago the AI SDK was mostly known for its <code class=\"language-text\">useChat</code> hook, the easy way to wire up a streaming chat UI in Next.js. Today it’s grown into a complete toolkit: a typed core for any TypeScript runtime, a UI layer that powers chat and assistant interfaces in any framework, an RSC layer for streaming React components from the server, and an agent loop for tool-using workflows. In this post we’ll walk through how the pieces fit together and the patterns that have become standard for shipping AI features in production Next.js apps.</p>\n<h3>What the SDK Actually Is</h3>\n<p>The Vercel AI SDK is split into three layers, each useful on its own:</p>\n<ul>\n<li><strong>AI SDK Core</strong> — A unified TypeScript API for calling LLMs. <code class=\"language-text\">generateText</code>, <code class=\"language-text\">streamText</code>, <code class=\"language-text\">generateObject</code>, <code class=\"language-text\">streamObject</code>, <code class=\"language-text\">embed</code>, and <code class=\"language-text\">embedMany</code> all work the same way regardless of provider.</li>\n<li><strong>AI SDK UI</strong> — Framework hooks (<code class=\"language-text\">useChat</code>, <code class=\"language-text\">useCompletion</code>, <code class=\"language-text\">useObject</code>, <code class=\"language-text\">useAssistant</code>) for React, Svelte, Vue, and Solid. Handles streaming, message state, optimistic updates, and error recovery.</li>\n<li><strong>AI SDK RSC</strong> — React Server Components helpers (<code class=\"language-text\">streamUI</code>, <code class=\"language-text\">createStreamableUI</code>, <code class=\"language-text\">createStreamableValue</code>) for streaming UI components from the server as the model generates them.</li>\n</ul>\n<p>You install only what you need. A backend cron job doing structured extraction needs Core but not UI. A Pages Router chat widget needs Core and UI but not RSC. A Next.js App Router app with generative UI uses all three.</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> ai @ai-sdk/openai @ai-sdk/anthropic zod</code></pre></div>\n<p>The provider packages are small adapters that translate the unified interface into provider-specific API calls. Adding a new provider means installing one more package, not rewriting your code.</p>\n<h3>The Core API</h3>\n<p>The lowest-level call is <code class=\"language-text\">generateText</code> — give it a model and a prompt, get text back:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> generateText <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ai'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> openai <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ai-sdk/openai'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> text<span class=\"token punctuation\">,</span> usage<span class=\"token punctuation\">,</span> finishReason <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">generateText</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  model<span class=\"token operator\">:</span> <span class=\"token function\">openai</span><span class=\"token punctuation\">(</span><span class=\"token string\">'gpt-5'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  prompt<span class=\"token operator\">:</span> <span class=\"token string\">'Explain quantum entanglement in two sentences.'</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token builtin\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>text<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token builtin\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">Used </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>usage<span class=\"token punctuation\">.</span>totalTokens<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> tokens</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Switching to Anthropic is a one-line change:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> anthropic <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ai-sdk/anthropic'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> text <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">generateText</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  model<span class=\"token operator\">:</span> <span class=\"token function\">anthropic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'claude-opus-4-7'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  prompt<span class=\"token operator\">:</span> <span class=\"token string\">'Explain quantum entanglement in two sentences.'</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>The return shape — <code class=\"language-text\">text</code>, <code class=\"language-text\">usage</code>, <code class=\"language-text\">finishReason</code>, <code class=\"language-text\">response</code>, <code class=\"language-text\">warnings</code> — is identical. The same goes for system prompts, conversation history, temperature, max tokens, stop sequences, and the rest of the standard knobs.</p>\n<h4>Streaming</h4>\n<p>For chat interfaces you almost always want <code class=\"language-text\">streamText</code> instead of <code class=\"language-text\">generateText</code>. It returns a result object with multiple consumption styles:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> streamText <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ai'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> openai <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ai-sdk/openai'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token constant\">POST</span><span class=\"token punctuation\">(</span>req<span class=\"token operator\">:</span> Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> messages <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> req<span class=\"token punctuation\">.</span><span class=\"token function\">json</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token function\">streamText</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    model<span class=\"token operator\">:</span> <span class=\"token function\">openai</span><span class=\"token punctuation\">(</span><span class=\"token string\">'gpt-5'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    system<span class=\"token operator\">:</span> <span class=\"token string\">'You are a helpful assistant.'</span><span class=\"token punctuation\">,</span>\n    messages<span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">return</span> result<span class=\"token punctuation\">.</span><span class=\"token function\">toUIMessageStreamResponse</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>The <code class=\"language-text\">toUIMessageStreamResponse()</code> helper returns a <code class=\"language-text\">Response</code> whose body is the AI SDK’s data stream protocol — a format the <code class=\"language-text\">useChat</code> hook on the client knows how to parse. You can also iterate the stream manually with <code class=\"language-text\">result.textStream</code> (just the text deltas) or <code class=\"language-text\">result.fullStream</code> (every event: text, tool calls, tool results, finish reasons).</p>\n<h4>Structured Output</h4>\n<p><code class=\"language-text\">generateObject</code> is one of the most useful tools in the SDK. Give it a Zod schema and it returns a typed, validated object:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> generateObject <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ai'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> z <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'zod'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> object <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">generateObject</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  model<span class=\"token operator\">:</span> <span class=\"token function\">openai</span><span class=\"token punctuation\">(</span><span class=\"token string\">'gpt-5'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  schema<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">object</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    recipe<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">object</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n      name<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">string</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      ingredients<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">array</span><span class=\"token punctuation\">(</span>z<span class=\"token punctuation\">.</span><span class=\"token function\">object</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n        name<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">string</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        amount<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">string</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      steps<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">array</span><span class=\"token punctuation\">(</span>z<span class=\"token punctuation\">.</span><span class=\"token function\">string</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      prepMinutes<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">number</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  prompt<span class=\"token operator\">:</span> <span class=\"token string\">'Generate a recipe for sourdough focaccia.'</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nobject<span class=\"token punctuation\">.</span>recipe<span class=\"token punctuation\">.</span>ingredients<span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>i<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token builtin\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>i<span class=\"token punctuation\">.</span>amount<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>i<span class=\"token punctuation\">.</span>name<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Under the hood the SDK uses each provider’s native structured output mode — OpenAI’s response format, Anthropic’s tool-call coercion, Google’s response schema — and validates the result against your Zod schema. Invalid responses throw a <code class=\"language-text\">NoObjectGeneratedError</code> you can catch and retry.</p>\n<p>For long objects there’s <code class=\"language-text\">streamObject</code>, which streams partial objects as they’re generated. The matching <code class=\"language-text\">useObject</code> hook on the client gives you a typed, progressively filling object that’s perfect for forms that auto-populate or dashboards that build themselves.</p>\n<h3>Tool Calling</h3>\n<p>Tool calling — letting the model invoke functions in your code — is where AI applications start feeling agentic. The SDK normalises tools across providers so you write them once:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> streamText<span class=\"token punctuation\">,</span> tool <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ai'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> z <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'zod'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token function\">streamText</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  model<span class=\"token operator\">:</span> <span class=\"token function\">openai</span><span class=\"token punctuation\">(</span><span class=\"token string\">'gpt-5'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  messages<span class=\"token punctuation\">,</span>\n  tools<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n    getWeather<span class=\"token operator\">:</span> <span class=\"token function\">tool</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n      description<span class=\"token operator\">:</span> <span class=\"token string\">'Get the current weather for a city'</span><span class=\"token punctuation\">,</span>\n      inputSchema<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">object</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n        city<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">string</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">describe</span><span class=\"token punctuation\">(</span><span class=\"token string\">'The city name'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        units<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">enum</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">'celsius'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'fahrenheit'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">default</span><span class=\"token punctuation\">(</span><span class=\"token string\">'celsius'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      <span class=\"token function-variable function\">execute</span><span class=\"token operator\">:</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> city<span class=\"token punctuation\">,</span> units <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">const</span> data <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">fetchWeather</span><span class=\"token punctuation\">(</span>city<span class=\"token punctuation\">,</span> units<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>\n          temperature<span class=\"token operator\">:</span> data<span class=\"token punctuation\">.</span>temp<span class=\"token punctuation\">,</span>\n          conditions<span class=\"token operator\">:</span> data<span class=\"token punctuation\">.</span>conditions<span class=\"token punctuation\">,</span>\n          humidity<span class=\"token operator\">:</span> data<span class=\"token punctuation\">.</span>humidity<span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    searchProducts<span class=\"token operator\">:</span> <span class=\"token function\">tool</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n      description<span class=\"token operator\">:</span> <span class=\"token string\">'Search the product catalog'</span><span class=\"token punctuation\">,</span>\n      inputSchema<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">object</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n        query<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">string</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        limit<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">number</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">default</span><span class=\"token punctuation\">(</span><span class=\"token number\">10</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      <span class=\"token function-variable function\">execute</span><span class=\"token operator\">:</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> query<span class=\"token punctuation\">,</span> limit <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token keyword\">await</span> db<span class=\"token punctuation\">.</span>products<span class=\"token punctuation\">.</span><span class=\"token function\">search</span><span class=\"token punctuation\">(</span>query<span class=\"token punctuation\">,</span> limit<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  stopWhen<span class=\"token operator\">:</span> <span class=\"token function\">stepCountIs</span><span class=\"token punctuation\">(</span><span class=\"token number\">5</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>The model sees tool descriptions and input schemas. When it decides to call one, the SDK invokes your <code class=\"language-text\">execute</code> function, captures the return value, and feeds it back as a tool result message. The <code class=\"language-text\">stopWhen</code> option caps how many tool-call rounds the model can perform before the loop ends — a critical safety valve. Without it a stuck model can chew through your budget calling the same tool forever.</p>\n<p>For tools that need user confirmation (transferring money, deleting data, sending an email), omit <code class=\"language-text\">execute</code> and handle the call on the client instead. The model proposes the call, your UI shows a confirmation dialog, and you submit the result back through the chat hook. This pattern keeps destructive actions human-in-the-loop without giving up the conversational flow.</p>\n<h3>The useChat Hook</h3>\n<p><code class=\"language-text\">useChat</code> is the AI SDK UI workhorse. It manages messages, handles streaming, surfaces tool calls, and gives you input state and submission handlers:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token string\">'use client'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> useChat <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ai-sdk/react'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">Chat</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> messages<span class=\"token punctuation\">,</span> input<span class=\"token punctuation\">,</span> handleInputChange<span class=\"token punctuation\">,</span> handleSubmit<span class=\"token punctuation\">,</span> status<span class=\"token punctuation\">,</span> stop <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span>\n    <span class=\"token function\">useChat</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> api<span class=\"token operator\">:</span> <span class=\"token string\">'/api/chat'</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">className</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>flex flex-col gap-4<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n      </span><span class=\"token punctuation\">{</span>messages<span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>m<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">(</span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">key</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>m<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">}</span></span> <span class=\"token attr-name\">className</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>m<span class=\"token punctuation\">.</span>role <span class=\"token operator\">===</span> <span class=\"token string\">'user'</span> <span class=\"token operator\">?</span> <span class=\"token string\">'text-right'</span> <span class=\"token operator\">:</span> <span class=\"token string\">''</span><span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n          </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>strong</span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">{</span>m<span class=\"token punctuation\">.</span>role<span class=\"token punctuation\">}</span><span class=\"token plain-text\">: </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>strong</span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n          </span><span class=\"token punctuation\">{</span>m<span class=\"token punctuation\">.</span>parts<span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>part<span class=\"token punctuation\">,</span> i<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>part<span class=\"token punctuation\">.</span>type <span class=\"token operator\">===</span> <span class=\"token string\">'text'</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">return</span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>span</span> <span class=\"token attr-name\">key</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>i<span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">{</span>part<span class=\"token punctuation\">.</span>text<span class=\"token punctuation\">}</span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>span</span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>part<span class=\"token punctuation\">.</span>type <span class=\"token operator\">===</span> <span class=\"token string\">'tool-call'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n              <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\n                <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>pre</span> <span class=\"token attr-name\">key</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>i<span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n                  Calling </span><span class=\"token punctuation\">{</span>part<span class=\"token punctuation\">.</span>toolName<span class=\"token punctuation\">}</span><span class=\"token plain-text\">(</span><span class=\"token punctuation\">{</span><span class=\"token constant\">JSON</span><span class=\"token punctuation\">.</span><span class=\"token function\">stringify</span><span class=\"token punctuation\">(</span>part<span class=\"token punctuation\">.</span>args<span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span><span class=\"token plain-text\">)\n                </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>pre</span><span class=\"token punctuation\">></span></span>\n              <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n            <span class=\"token keyword\">return</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\n          <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span><span class=\"token plain-text\">\n        </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span><span class=\"token plain-text\">\n\n      </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span> <span class=\"token attr-name\">onSubmit</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>handleSubmit<span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n        </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span>\n          <span class=\"token attr-name\">value</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>input<span class=\"token punctuation\">}</span></span>\n          <span class=\"token attr-name\">onChange</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>handleInputChange<span class=\"token punctuation\">}</span></span>\n          <span class=\"token attr-name\">placeholder</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Ask something...<span class=\"token punctuation\">\"</span></span>\n          <span class=\"token attr-name\">disabled</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>status <span class=\"token operator\">!==</span> <span class=\"token string\">'ready'</span><span class=\"token punctuation\">}</span></span>\n        <span class=\"token punctuation\">/></span></span><span class=\"token plain-text\">\n        </span><span class=\"token punctuation\">{</span>status <span class=\"token operator\">===</span> <span class=\"token string\">'streaming'</span> <span class=\"token operator\">&amp;&amp;</span> <span class=\"token punctuation\">(</span>\n          <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>button</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>button<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">onClick</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>stop<span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n            Stop\n          </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>button</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span><span class=\"token plain-text\">\n      </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span><span class=\"token plain-text\">\n    </span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>The <code class=\"language-text\">messages</code> array uses a <code class=\"language-text\">parts</code>-based shape so a single assistant message can include text, tool calls, tool results, reasoning steps, and inline files. Rendering by part type is the recommended pattern — it scales cleanly as you add more capabilities.</p>\n<p>The <code class=\"language-text\">status</code> field has four values: <code class=\"language-text\">submitted</code>, <code class=\"language-text\">streaming</code>, <code class=\"language-text\">ready</code>, <code class=\"language-text\">error</code>. Use it to disable the input while a request is in flight, show a typing indicator, and expose a stop button so users can interrupt long responses.</p>\n<p>Need to persist conversations? Pass <code class=\"language-text\">id</code> and <code class=\"language-text\">initialMessages</code> from a server-loaded conversation. The hook handles the rest, including reconciling new server-side messages once the stream completes.</p>\n<h3>Generative UI with React Server Components</h3>\n<p>The most distinctive AI SDK feature is generative UI — letting the model stream actual React components from the server, not just text. <code class=\"language-text\">streamUI</code> is the entry point:</p>\n<div class=\"gatsby-highlight\" data-language=\"tsx\"><pre class=\"language-tsx\"><code class=\"language-tsx\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> streamUI <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ai/rsc'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> openai <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ai-sdk/openai'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> z <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'zod'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> WeatherCard <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@/components/weather-card'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> ProductList <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@/components/product-list'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">ask</span><span class=\"token punctuation\">(</span>question<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token string\">'use server'</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">streamUI</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    model<span class=\"token operator\">:</span> <span class=\"token function\">openai</span><span class=\"token punctuation\">(</span><span class=\"token string\">'gpt-5'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    prompt<span class=\"token operator\">:</span> question<span class=\"token punctuation\">,</span>\n    <span class=\"token function-variable function\">text</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> content <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">{</span>content<span class=\"token punctuation\">}</span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">,</span>\n    tools<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      showWeather<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        description<span class=\"token operator\">:</span> <span class=\"token string\">'Display a weather card for a city'</span><span class=\"token punctuation\">,</span>\n        inputSchema<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">object</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> city<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">string</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        <span class=\"token function-variable function\">generate</span><span class=\"token operator\">:</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span><span class=\"token operator\">*</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> city <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n          <span class=\"token keyword\">yield</span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">WeatherCard</span></span> <span class=\"token attr-name\">city</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>city<span class=\"token punctuation\">}</span></span> <span class=\"token attr-name\">loading</span> <span class=\"token punctuation\">/></span></span><span class=\"token punctuation\">;</span>\n          <span class=\"token keyword\">const</span> data <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">fetchWeather</span><span class=\"token punctuation\">(</span>city<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n          <span class=\"token keyword\">return</span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">WeatherCard</span></span> <span class=\"token attr-name\">city</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>city<span class=\"token punctuation\">}</span></span> <span class=\"token attr-name\">data</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>data<span class=\"token punctuation\">}</span></span> <span class=\"token punctuation\">/></span></span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      showProducts<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        description<span class=\"token operator\">:</span> <span class=\"token string\">'Display matching products'</span><span class=\"token punctuation\">,</span>\n        inputSchema<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">object</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> query<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">string</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        <span class=\"token function-variable function\">generate</span><span class=\"token operator\">:</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> query <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n          <span class=\"token keyword\">const</span> products <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> db<span class=\"token punctuation\">.</span>products<span class=\"token punctuation\">.</span><span class=\"token function\">search</span><span class=\"token punctuation\">(</span>query<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n          <span class=\"token keyword\">return</span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">ProductList</span></span> <span class=\"token attr-name\">products</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span>products<span class=\"token punctuation\">}</span></span> <span class=\"token punctuation\">/></span></span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">return</span> result<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>The model chooses which tool to call, and the <code class=\"language-text\">generate</code> function returns a React element (or yields multiple, for progressive states). The element is streamed to the client and rendered in place. You can yield a loading skeleton, fetch data, then yield the populated component — all from one async generator.</p>\n<p>This sidesteps the usual “model returns JSON, client renders a card” dance entirely. The server owns the rendering, which means you can use server-only data sources, keep API keys out of the bundle, and ship richer UI without re-architecting your data layer.</p>\n<h3>Working with Embeddings</h3>\n<p>The SDK isn’t just for chat — it’s also the simplest way to generate embeddings for semantic search, retrieval, and clustering:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> embedMany <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ai'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> openai <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ai-sdk/openai'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> embeddings<span class=\"token punctuation\">,</span> usage <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">embedMany</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  model<span class=\"token operator\">:</span> openai<span class=\"token punctuation\">.</span><span class=\"token function\">embedding</span><span class=\"token punctuation\">(</span><span class=\"token string\">'text-embedding-3-large'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  values<span class=\"token operator\">:</span> docs<span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>d<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> d<span class=\"token punctuation\">.</span>content<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">await</span> db<span class=\"token punctuation\">.</span>documents<span class=\"token punctuation\">.</span><span class=\"token function\">insertMany</span><span class=\"token punctuation\">(</span>\n  docs<span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>d<span class=\"token punctuation\">,</span> i<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    <span class=\"token operator\">...</span>d<span class=\"token punctuation\">,</span>\n    embedding<span class=\"token operator\">:</span> embeddings<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>For a query, use <code class=\"language-text\">embed</code> for a single vector and run a similarity search against your vector store. The Drizzle and Prisma ecosystems both support <code class=\"language-text\">pgvector</code> columns now, so this slots cleanly into a normal Postgres-backed Next.js app without a separate vector database.</p>\n<h3>Provider Switching and the AI Gateway</h3>\n<p>Because every provider exposes the same interface, switching is a matter of changing the model passed to a call. In practice teams take this further with a small wrapper that selects a model based on environment or feature flag:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> openai <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ai-sdk/openai'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> anthropic <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ai-sdk/anthropic'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> google <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ai-sdk/google'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">pickModel</span><span class=\"token punctuation\">(</span>task<span class=\"token operator\">:</span> <span class=\"token string\">'fast'</span> <span class=\"token operator\">|</span> <span class=\"token string\">'smart'</span> <span class=\"token operator\">|</span> <span class=\"token string\">'cheap'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">switch</span> <span class=\"token punctuation\">(</span>task<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">case</span> <span class=\"token string\">'fast'</span><span class=\"token operator\">:</span>\n      <span class=\"token keyword\">return</span> <span class=\"token function\">google</span><span class=\"token punctuation\">(</span><span class=\"token string\">'gemini-2.5-flash'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">case</span> <span class=\"token string\">'smart'</span><span class=\"token operator\">:</span>\n      <span class=\"token keyword\">return</span> <span class=\"token function\">anthropic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'claude-opus-4-7'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">case</span> <span class=\"token string\">'cheap'</span><span class=\"token operator\">:</span>\n      <span class=\"token keyword\">return</span> <span class=\"token function\">openai</span><span class=\"token punctuation\">(</span><span class=\"token string\">'gpt-5-mini'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> text <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">generateText</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  model<span class=\"token operator\">:</span> <span class=\"token function\">pickModel</span><span class=\"token punctuation\">(</span><span class=\"token string\">'smart'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  prompt<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Vercel’s hosted <strong>AI Gateway</strong> takes this one step further. It provides a single endpoint, a single API key, automatic failover between providers, request-level analytics, and unified billing. You point your AI SDK provider at the gateway and gain provider-agnostic observability without changing application code:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> createGateway <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ai-sdk/gateway'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> gateway <span class=\"token operator\">=</span> <span class=\"token function\">createGateway</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> apiKey<span class=\"token operator\">:</span> process<span class=\"token punctuation\">.</span>env<span class=\"token punctuation\">.</span><span class=\"token constant\">AI_GATEWAY_KEY</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> text <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">generateText</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  model<span class=\"token operator\">:</span> <span class=\"token function\">gateway</span><span class=\"token punctuation\">(</span><span class=\"token string\">'anthropic/claude-opus-4-7'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  prompt<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>If the upstream provider is down, the gateway can route to a fallback model you configure. For production apps this turns model outages from a Sev 1 into a logged warning.</p>\n<h3>Agentic Loops</h3>\n<p>Multi-step agents — model calls a tool, sees the result, calls another tool, eventually answers — are first-class in the SDK. <code class=\"language-text\">streamText</code> with tools already loops automatically up to your <code class=\"language-text\">stopWhen</code> limit, but for richer control there’s the <code class=\"language-text\">experimental_Agent</code> class:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Agent <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ai'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> supportAgent <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Agent</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  model<span class=\"token operator\">:</span> <span class=\"token function\">anthropic</span><span class=\"token punctuation\">(</span><span class=\"token string\">'claude-opus-4-7'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  system<span class=\"token operator\">:</span> <span class=\"token string\">'You are a support engineer. Use the tools to investigate.'</span><span class=\"token punctuation\">,</span>\n  tools<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n    searchTickets<span class=\"token punctuation\">,</span>\n    readLogs<span class=\"token punctuation\">,</span>\n    queryDatabase<span class=\"token punctuation\">,</span>\n    pageOncall<span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  stopWhen<span class=\"token operator\">:</span> <span class=\"token function\">stepCountIs</span><span class=\"token punctuation\">(</span><span class=\"token number\">10</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> text<span class=\"token punctuation\">,</span> steps <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> supportAgent<span class=\"token punctuation\">.</span><span class=\"token function\">generate</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  prompt<span class=\"token operator\">:</span> <span class=\"token string\">'Investigate why customer 4271 cannot log in.'</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Each step in <code class=\"language-text\">steps</code> includes the model’s reasoning, the tool calls it made, and their results. Log these to your observability platform and you get an audit trail of every decision the agent took — invaluable when something goes wrong and you need to debug an autonomous workflow.</p>\n<h3>Middleware and Observability</h3>\n<p>Every model call passes through any middleware you register, which is where caching, logging, and guardrails live:</p>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> wrapLanguageModel<span class=\"token punctuation\">,</span> <span class=\"token keyword\">type</span> <span class=\"token class-name\">LanguageModelV2Middleware</span> <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ai'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> cacheMiddleware<span class=\"token operator\">:</span> LanguageModelV2Middleware <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function-variable function\">wrapGenerate</span><span class=\"token operator\">:</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> doGenerate<span class=\"token punctuation\">,</span> params <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> key <span class=\"token operator\">=</span> <span class=\"token function\">hashParams</span><span class=\"token punctuation\">(</span>params<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> cached <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> redis<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span>key<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>cached<span class=\"token punctuation\">)</span> <span class=\"token keyword\">return</span> <span class=\"token constant\">JSON</span><span class=\"token punctuation\">.</span><span class=\"token function\">parse</span><span class=\"token punctuation\">(</span>cached<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">doGenerate</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">await</span> redis<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>key<span class=\"token punctuation\">,</span> <span class=\"token constant\">JSON</span><span class=\"token punctuation\">.</span><span class=\"token function\">stringify</span><span class=\"token punctuation\">(</span>result<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'EX'</span><span class=\"token punctuation\">,</span> <span class=\"token number\">3600</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> result<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> model <span class=\"token operator\">=</span> <span class=\"token function\">wrapLanguageModel</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  model<span class=\"token operator\">:</span> <span class=\"token function\">openai</span><span class=\"token punctuation\">(</span><span class=\"token string\">'gpt-5'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  middleware<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>cacheMiddleware<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>For telemetry, pass <code class=\"language-text\">experimental_telemetry: { isEnabled: true }</code> to any call and the SDK emits OpenTelemetry spans for the request, tool calls, and token usage. Pipe those into Langfuse, Helicone, or PostHog LLM Analytics and you have full observability of every model interaction without bespoke instrumentation.</p>\n<h3>Comparison with Raw Provider SDKs</h3>\n<p>When does the AI SDK earn its keep over calling provider SDKs directly?</p>\n<table>\n<thead>\n<tr>\n<th>Concern</th>\n<th>Raw SDK</th>\n<th>Vercel AI SDK</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Provider switching</td>\n<td>Rewrite call site</td>\n<td>Change one import</td>\n</tr>\n<tr>\n<td>Streaming protocol</td>\n<td>Provider-specific SSE</td>\n<td>Unified data stream</td>\n</tr>\n<tr>\n<td>Tool calling</td>\n<td>Different shape per provider</td>\n<td>One <code class=\"language-text\">tool()</code> helper</td>\n</tr>\n<tr>\n<td>Structured output</td>\n<td>Manual schema handling</td>\n<td><code class=\"language-text\">generateObject</code> with Zod</td>\n</tr>\n<tr>\n<td>React integration</td>\n<td>Build it yourself</td>\n<td><code class=\"language-text\">useChat</code>, <code class=\"language-text\">useObject</code></td>\n</tr>\n<tr>\n<td>RSC streaming</td>\n<td>Build it yourself</td>\n<td><code class=\"language-text\">streamUI</code></td>\n</tr>\n<tr>\n<td>Multi-step agents</td>\n<td>Build the loop</td>\n<td><code class=\"language-text\">Agent</code> + <code class=\"language-text\">stopWhen</code></td>\n</tr>\n</tbody>\n</table>\n<p>The trade-off is a thin abstraction layer between you and the model. For 95% of applications that’s a win — the SDK exposes every provider feature that matters and adds capabilities the raw SDKs lack. The exceptions are research code that needs the absolute newest provider-specific feature within hours of release, and apps that only ever talk to one model and want zero dependencies.</p>\n<h3>Production Patterns</h3>\n<p>A few patterns that have become standard once you’re past the first prototype:</p>\n<ol>\n<li><strong>Always set <code class=\"language-text\">stopWhen</code></strong> on tool-enabled calls. Stuck loops are the most common way to burn a budget.</li>\n<li><strong>Validate inputs with Zod</strong>, even ones the model “should” get right. Use <code class=\"language-text\">.describe()</code> on each field — the model reads descriptions and they noticeably improve tool-call accuracy.</li>\n<li><strong>Surface tool calls in the UI</strong> rather than hiding them. Users trust agents more when they can see what’s being looked up.</li>\n<li><strong>Persist messages server-side</strong> by hooking <code class=\"language-text\">onFinish</code> in your route handler to write the assistant response to your database. Don’t rely on the client to round-trip the full transcript.</li>\n<li><strong>Use <code class=\"language-text\">experimental_telemetry</code></strong> from day one. Cost surprises are easier to diagnose when every call is traced.</li>\n<li><strong>Cache aggressively</strong> for prompts that don’t depend on user input. A middleware cache on a system-prompt summarisation call can cut your bill by an order of magnitude.</li>\n</ol>\n<h3>When Not to Use It</h3>\n<p>There are still cases where reaching for the AI SDK is overkill. A one-off script that calls OpenAI once and exits is fine with the raw <code class=\"language-text\">openai</code> package. Edge functions where every kilobyte counts can benefit from skipping the abstraction. And if you’re building a non-React Vue or vanilla TypeScript app, AI SDK UI is less compelling — though Core still wins on provider abstraction.</p>\n<p>For anything beyond a single call from a single provider in a frontend codebase, the SDK pays for itself within an afternoon of work saved.</p>\n<h3>Conclusion</h3>\n<p>The Vercel AI SDK has quietly become the default way to ship AI features in a TypeScript app. It papers over the provider differences that make integration tedious, gives you a real React hook for chat instead of a DIY SSE parser, and unlocks patterns — generative UI, streamed structured output, agentic loops — that would be weeks of work to build from scratch.</p>\n<p>Pair it with the AI Gateway for production routing, with PostHog LLM Analytics for observability, and with Zod for input validation, and you have a stack that handles every layer of an AI feature from the model call up to the rendered UI. The model landscape will keep churning — new providers, new modalities, new capabilities — but the SDK absorbs that churn so your application code doesn’t have to.</p>\n<p>‘Till next time!</p>","rawMarkdownBody":"\n## A Unified Toolkit for AI in TypeScript\n\n<p class=\"lead\">Every model provider ships its own SDK with its own quirks. OpenAI streams differently than Anthropic, Anthropic handles tools differently than Google, and the moment you want to swap providers you end up rewriting half your application. The Vercel AI SDK exists to make that pain go away — one API for every model, with first-class React hooks layered on top.</p>\n\nA year ago the AI SDK was mostly known for its `useChat` hook, the easy way to wire up a streaming chat UI in Next.js. Today it's grown into a complete toolkit: a typed core for any TypeScript runtime, a UI layer that powers chat and assistant interfaces in any framework, an RSC layer for streaming React components from the server, and an agent loop for tool-using workflows. In this post we'll walk through how the pieces fit together and the patterns that have become standard for shipping AI features in production Next.js apps.\n\n### What the SDK Actually Is\n\nThe Vercel AI SDK is split into three layers, each useful on its own:\n\n- **AI SDK Core** — A unified TypeScript API for calling LLMs. `generateText`, `streamText`, `generateObject`, `streamObject`, `embed`, and `embedMany` all work the same way regardless of provider.\n- **AI SDK UI** — Framework hooks (`useChat`, `useCompletion`, `useObject`, `useAssistant`) for React, Svelte, Vue, and Solid. Handles streaming, message state, optimistic updates, and error recovery.\n- **AI SDK RSC** — React Server Components helpers (`streamUI`, `createStreamableUI`, `createStreamableValue`) for streaming UI components from the server as the model generates them.\n\nYou install only what you need. A backend cron job doing structured extraction needs Core but not UI. A Pages Router chat widget needs Core and UI but not RSC. A Next.js App Router app with generative UI uses all three.\n\n```bash\nnpm install ai @ai-sdk/openai @ai-sdk/anthropic zod\n```\n\nThe provider packages are small adapters that translate the unified interface into provider-specific API calls. Adding a new provider means installing one more package, not rewriting your code.\n\n### The Core API\n\nThe lowest-level call is `generateText` — give it a model and a prompt, get text back:\n\n```typescript\nimport { generateText } from 'ai';\nimport { openai } from '@ai-sdk/openai';\n\nconst { text, usage, finishReason } = await generateText({\n  model: openai('gpt-5'),\n  prompt: 'Explain quantum entanglement in two sentences.',\n});\n\nconsole.log(text);\nconsole.log(`Used ${usage.totalTokens} tokens`);\n```\n\nSwitching to Anthropic is a one-line change:\n\n```typescript\nimport { anthropic } from '@ai-sdk/anthropic';\n\nconst { text } = await generateText({\n  model: anthropic('claude-opus-4-7'),\n  prompt: 'Explain quantum entanglement in two sentences.',\n});\n```\n\nThe return shape — `text`, `usage`, `finishReason`, `response`, `warnings` — is identical. The same goes for system prompts, conversation history, temperature, max tokens, stop sequences, and the rest of the standard knobs.\n\n#### Streaming\n\nFor chat interfaces you almost always want `streamText` instead of `generateText`. It returns a result object with multiple consumption styles:\n\n```typescript\nimport { streamText } from 'ai';\nimport { openai } from '@ai-sdk/openai';\n\nexport async function POST(req: Request) {\n  const { messages } = await req.json();\n\n  const result = streamText({\n    model: openai('gpt-5'),\n    system: 'You are a helpful assistant.',\n    messages,\n  });\n\n  return result.toUIMessageStreamResponse();\n}\n```\n\nThe `toUIMessageStreamResponse()` helper returns a `Response` whose body is the AI SDK's data stream protocol — a format the `useChat` hook on the client knows how to parse. You can also iterate the stream manually with `result.textStream` (just the text deltas) or `result.fullStream` (every event: text, tool calls, tool results, finish reasons).\n\n#### Structured Output\n\n`generateObject` is one of the most useful tools in the SDK. Give it a Zod schema and it returns a typed, validated object:\n\n```typescript\nimport { generateObject } from 'ai';\nimport { z } from 'zod';\n\nconst { object } = await generateObject({\n  model: openai('gpt-5'),\n  schema: z.object({\n    recipe: z.object({\n      name: z.string(),\n      ingredients: z.array(z.object({\n        name: z.string(),\n        amount: z.string(),\n      })),\n      steps: z.array(z.string()),\n      prepMinutes: z.number(),\n    }),\n  }),\n  prompt: 'Generate a recipe for sourdough focaccia.',\n});\n\nobject.recipe.ingredients.forEach((i) => {\n  console.log(`${i.amount} ${i.name}`);\n});\n```\n\nUnder the hood the SDK uses each provider's native structured output mode — OpenAI's response format, Anthropic's tool-call coercion, Google's response schema — and validates the result against your Zod schema. Invalid responses throw a `NoObjectGeneratedError` you can catch and retry.\n\nFor long objects there's `streamObject`, which streams partial objects as they're generated. The matching `useObject` hook on the client gives you a typed, progressively filling object that's perfect for forms that auto-populate or dashboards that build themselves.\n\n### Tool Calling\n\nTool calling — letting the model invoke functions in your code — is where AI applications start feeling agentic. The SDK normalises tools across providers so you write them once:\n\n```typescript\nimport { streamText, tool } from 'ai';\nimport { z } from 'zod';\n\nconst result = streamText({\n  model: openai('gpt-5'),\n  messages,\n  tools: {\n    getWeather: tool({\n      description: 'Get the current weather for a city',\n      inputSchema: z.object({\n        city: z.string().describe('The city name'),\n        units: z.enum(['celsius', 'fahrenheit']).default('celsius'),\n      }),\n      execute: async ({ city, units }) => {\n        const data = await fetchWeather(city, units);\n        return {\n          temperature: data.temp,\n          conditions: data.conditions,\n          humidity: data.humidity,\n        };\n      },\n    }),\n    searchProducts: tool({\n      description: 'Search the product catalog',\n      inputSchema: z.object({\n        query: z.string(),\n        limit: z.number().default(10),\n      }),\n      execute: async ({ query, limit }) => {\n        return await db.products.search(query, limit);\n      },\n    }),\n  },\n  stopWhen: stepCountIs(5),\n});\n```\n\nThe model sees tool descriptions and input schemas. When it decides to call one, the SDK invokes your `execute` function, captures the return value, and feeds it back as a tool result message. The `stopWhen` option caps how many tool-call rounds the model can perform before the loop ends — a critical safety valve. Without it a stuck model can chew through your budget calling the same tool forever.\n\nFor tools that need user confirmation (transferring money, deleting data, sending an email), omit `execute` and handle the call on the client instead. The model proposes the call, your UI shows a confirmation dialog, and you submit the result back through the chat hook. This pattern keeps destructive actions human-in-the-loop without giving up the conversational flow.\n\n### The useChat Hook\n\n`useChat` is the AI SDK UI workhorse. It manages messages, handles streaming, surfaces tool calls, and gives you input state and submission handlers:\n\n```tsx\n'use client';\n\nimport { useChat } from '@ai-sdk/react';\n\nexport function Chat() {\n  const { messages, input, handleInputChange, handleSubmit, status, stop } =\n    useChat({ api: '/api/chat' });\n\n  return (\n    <div className=\"flex flex-col gap-4\">\n      {messages.map((m) => (\n        <div key={m.id} className={m.role === 'user' ? 'text-right' : ''}>\n          <strong>{m.role}: </strong>\n          {m.parts.map((part, i) => {\n            if (part.type === 'text') return <span key={i}>{part.text}</span>;\n            if (part.type === 'tool-call') {\n              return (\n                <pre key={i}>\n                  Calling {part.toolName}({JSON.stringify(part.args)})\n                </pre>\n              );\n            }\n            return null;\n          })}\n        </div>\n      ))}\n\n      <form onSubmit={handleSubmit}>\n        <input\n          value={input}\n          onChange={handleInputChange}\n          placeholder=\"Ask something...\"\n          disabled={status !== 'ready'}\n        />\n        {status === 'streaming' && (\n          <button type=\"button\" onClick={stop}>\n            Stop\n          </button>\n        )}\n      </form>\n    </div>\n  );\n}\n```\n\nThe `messages` array uses a `parts`-based shape so a single assistant message can include text, tool calls, tool results, reasoning steps, and inline files. Rendering by part type is the recommended pattern — it scales cleanly as you add more capabilities.\n\nThe `status` field has four values: `submitted`, `streaming`, `ready`, `error`. Use it to disable the input while a request is in flight, show a typing indicator, and expose a stop button so users can interrupt long responses.\n\nNeed to persist conversations? Pass `id` and `initialMessages` from a server-loaded conversation. The hook handles the rest, including reconciling new server-side messages once the stream completes.\n\n### Generative UI with React Server Components\n\nThe most distinctive AI SDK feature is generative UI — letting the model stream actual React components from the server, not just text. `streamUI` is the entry point:\n\n```tsx\nimport { streamUI } from 'ai/rsc';\nimport { openai } from '@ai-sdk/openai';\nimport { z } from 'zod';\nimport { WeatherCard } from '@/components/weather-card';\nimport { ProductList } from '@/components/product-list';\n\nexport async function ask(question: string) {\n  'use server';\n\n  const result = await streamUI({\n    model: openai('gpt-5'),\n    prompt: question,\n    text: ({ content }) => <p>{content}</p>,\n    tools: {\n      showWeather: {\n        description: 'Display a weather card for a city',\n        inputSchema: z.object({ city: z.string() }),\n        generate: async function* ({ city }) {\n          yield <WeatherCard city={city} loading />;\n          const data = await fetchWeather(city);\n          return <WeatherCard city={city} data={data} />;\n        },\n      },\n      showProducts: {\n        description: 'Display matching products',\n        inputSchema: z.object({ query: z.string() }),\n        generate: async ({ query }) => {\n          const products = await db.products.search(query);\n          return <ProductList products={products} />;\n        },\n      },\n    },\n  });\n\n  return result.value;\n}\n```\n\nThe model chooses which tool to call, and the `generate` function returns a React element (or yields multiple, for progressive states). The element is streamed to the client and rendered in place. You can yield a loading skeleton, fetch data, then yield the populated component — all from one async generator.\n\nThis sidesteps the usual \"model returns JSON, client renders a card\" dance entirely. The server owns the rendering, which means you can use server-only data sources, keep API keys out of the bundle, and ship richer UI without re-architecting your data layer.\n\n### Working with Embeddings\n\nThe SDK isn't just for chat — it's also the simplest way to generate embeddings for semantic search, retrieval, and clustering:\n\n```typescript\nimport { embedMany } from 'ai';\nimport { openai } from '@ai-sdk/openai';\n\nconst { embeddings, usage } = await embedMany({\n  model: openai.embedding('text-embedding-3-large'),\n  values: docs.map((d) => d.content),\n});\n\nawait db.documents.insertMany(\n  docs.map((d, i) => ({\n    ...d,\n    embedding: embeddings[i],\n  }))\n);\n```\n\nFor a query, use `embed` for a single vector and run a similarity search against your vector store. The Drizzle and Prisma ecosystems both support `pgvector` columns now, so this slots cleanly into a normal Postgres-backed Next.js app without a separate vector database.\n\n### Provider Switching and the AI Gateway\n\nBecause every provider exposes the same interface, switching is a matter of changing the model passed to a call. In practice teams take this further with a small wrapper that selects a model based on environment or feature flag:\n\n```typescript\nimport { openai } from '@ai-sdk/openai';\nimport { anthropic } from '@ai-sdk/anthropic';\nimport { google } from '@ai-sdk/google';\n\nfunction pickModel(task: 'fast' | 'smart' | 'cheap') {\n  switch (task) {\n    case 'fast':\n      return google('gemini-2.5-flash');\n    case 'smart':\n      return anthropic('claude-opus-4-7');\n    case 'cheap':\n      return openai('gpt-5-mini');\n  }\n}\n\nconst { text } = await generateText({\n  model: pickModel('smart'),\n  prompt,\n});\n```\n\nVercel's hosted **AI Gateway** takes this one step further. It provides a single endpoint, a single API key, automatic failover between providers, request-level analytics, and unified billing. You point your AI SDK provider at the gateway and gain provider-agnostic observability without changing application code:\n\n```typescript\nimport { createGateway } from '@ai-sdk/gateway';\n\nconst gateway = createGateway({ apiKey: process.env.AI_GATEWAY_KEY });\n\nconst { text } = await generateText({\n  model: gateway('anthropic/claude-opus-4-7'),\n  prompt,\n});\n```\n\nIf the upstream provider is down, the gateway can route to a fallback model you configure. For production apps this turns model outages from a Sev 1 into a logged warning.\n\n### Agentic Loops\n\nMulti-step agents — model calls a tool, sees the result, calls another tool, eventually answers — are first-class in the SDK. `streamText` with tools already loops automatically up to your `stopWhen` limit, but for richer control there's the `experimental_Agent` class:\n\n```typescript\nimport { Agent } from 'ai';\n\nconst supportAgent = new Agent({\n  model: anthropic('claude-opus-4-7'),\n  system: 'You are a support engineer. Use the tools to investigate.',\n  tools: {\n    searchTickets,\n    readLogs,\n    queryDatabase,\n    pageOncall,\n  },\n  stopWhen: stepCountIs(10),\n});\n\nconst { text, steps } = await supportAgent.generate({\n  prompt: 'Investigate why customer 4271 cannot log in.',\n});\n```\n\nEach step in `steps` includes the model's reasoning, the tool calls it made, and their results. Log these to your observability platform and you get an audit trail of every decision the agent took — invaluable when something goes wrong and you need to debug an autonomous workflow.\n\n### Middleware and Observability\n\nEvery model call passes through any middleware you register, which is where caching, logging, and guardrails live:\n\n```typescript\nimport { wrapLanguageModel, type LanguageModelV2Middleware } from 'ai';\n\nconst cacheMiddleware: LanguageModelV2Middleware = {\n  wrapGenerate: async ({ doGenerate, params }) => {\n    const key = hashParams(params);\n    const cached = await redis.get(key);\n    if (cached) return JSON.parse(cached);\n\n    const result = await doGenerate();\n    await redis.set(key, JSON.stringify(result), 'EX', 3600);\n    return result;\n  },\n};\n\nconst model = wrapLanguageModel({\n  model: openai('gpt-5'),\n  middleware: [cacheMiddleware],\n});\n```\n\nFor telemetry, pass `experimental_telemetry: { isEnabled: true }` to any call and the SDK emits OpenTelemetry spans for the request, tool calls, and token usage. Pipe those into Langfuse, Helicone, or PostHog LLM Analytics and you have full observability of every model interaction without bespoke instrumentation.\n\n### Comparison with Raw Provider SDKs\n\nWhen does the AI SDK earn its keep over calling provider SDKs directly?\n\n| Concern | Raw SDK | Vercel AI SDK |\n| --- | --- | --- |\n| Provider switching | Rewrite call site | Change one import |\n| Streaming protocol | Provider-specific SSE | Unified data stream |\n| Tool calling | Different shape per provider | One `tool()` helper |\n| Structured output | Manual schema handling | `generateObject` with Zod |\n| React integration | Build it yourself | `useChat`, `useObject` |\n| RSC streaming | Build it yourself | `streamUI` |\n| Multi-step agents | Build the loop | `Agent` + `stopWhen` |\n\nThe trade-off is a thin abstraction layer between you and the model. For 95% of applications that's a win — the SDK exposes every provider feature that matters and adds capabilities the raw SDKs lack. The exceptions are research code that needs the absolute newest provider-specific feature within hours of release, and apps that only ever talk to one model and want zero dependencies.\n\n### Production Patterns\n\nA few patterns that have become standard once you're past the first prototype:\n\n1. **Always set `stopWhen`** on tool-enabled calls. Stuck loops are the most common way to burn a budget.\n2. **Validate inputs with Zod**, even ones the model \"should\" get right. Use `.describe()` on each field — the model reads descriptions and they noticeably improve tool-call accuracy.\n3. **Surface tool calls in the UI** rather than hiding them. Users trust agents more when they can see what's being looked up.\n4. **Persist messages server-side** by hooking `onFinish` in your route handler to write the assistant response to your database. Don't rely on the client to round-trip the full transcript.\n5. **Use `experimental_telemetry`** from day one. Cost surprises are easier to diagnose when every call is traced.\n6. **Cache aggressively** for prompts that don't depend on user input. A middleware cache on a system-prompt summarisation call can cut your bill by an order of magnitude.\n\n### When Not to Use It\n\nThere are still cases where reaching for the AI SDK is overkill. A one-off script that calls OpenAI once and exits is fine with the raw `openai` package. Edge functions where every kilobyte counts can benefit from skipping the abstraction. And if you're building a non-React Vue or vanilla TypeScript app, AI SDK UI is less compelling — though Core still wins on provider abstraction.\n\nFor anything beyond a single call from a single provider in a frontend codebase, the SDK pays for itself within an afternoon of work saved.\n\n### Conclusion\n\nThe Vercel AI SDK has quietly become the default way to ship AI features in a TypeScript app. It papers over the provider differences that make integration tedious, gives you a real React hook for chat instead of a DIY SSE parser, and unlocks patterns — generative UI, streamed structured output, agentic loops — that would be weeks of work to build from scratch.\n\nPair it with the AI Gateway for production routing, with PostHog LLM Analytics for observability, and with Zod for input validation, and you have a stack that handles every layer of an AI feature from the model call up to the rendered UI. The model landscape will keep churning — new providers, new modalities, new capabilities — but the SDK absorbs that churn so your application code doesn't have to.\n\n'Till next time!\n","frontmatter":{"title":"Building AI-Powered Apps with the Vercel AI SDK","date":"23. May, 2026","description":"Stream AI responses, call tools, and render generative UI with the Vercel AI SDK. Providers, useChat, generateObject, RSC streaming, and agentic loops.","category":"Develop","cover":{"childImageSharp":{"gatsbyImageData":{"layout":"fixed","backgroundColor":"#082858","images":{"fallback":{"src":"/static/fa686e0be34aee16c200c8d56643688d/1619f/vercel-ai-sdk.png","srcSet":"/static/fa686e0be34aee16c200c8d56643688d/1619f/vercel-ai-sdk.png 960w","sizes":"960px"},"sources":[{"srcSet":"/static/fa686e0be34aee16c200c8d56643688d/0a27d/vercel-ai-sdk.webp 960w","type":"image/webp","sizes":"960px"}]},"width":960,"height":653}}}},"fields":{"slug":"/2026-05-23_vercel-ai-sdk/"}}},"pageContext":{"slug":"/2026-05-23_vercel-ai-sdk/","previous":{"fields":{"slug":"/2026-04-18_claude-code/"},"frontmatter":{"title":"Supercharging Your Workflow with Claude Code Skills"}},"next":null}},
    "staticQueryHashes": ["1711471402","674253978"]}