<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Structured Output on RockB</title><link>https://baeseokjae.github.io/tags/structured-output/</link><description>Recent content in Structured Output on RockB</description><image><title>RockB</title><url>https://baeseokjae.github.io/images/og-default.png</url><link>https://baeseokjae.github.io/images/og-default.png</link></image><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 21 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://baeseokjae.github.io/tags/structured-output/index.xml" rel="self" type="application/rss+xml"/><item><title>Vercel AI SDK Guide 2026: Build Production AI Apps with TypeScript</title><link>https://baeseokjae.github.io/posts/vercel-ai-sdk-guide-2026/</link><pubDate>Tue, 21 Apr 2026 00:00:00 +0000</pubDate><guid>https://baeseokjae.github.io/posts/vercel-ai-sdk-guide-2026/</guid><description>A complete guide to building production AI applications with the Vercel AI SDK in 2026. Covers text generation, streaming, structured output with Zod, chat UIs, tool calling, multi-step agents, Workflows, and deployment.</description><content:encoded><![CDATA[<p>The Vercel AI SDK has become the default way to build AI-powered applications in TypeScript. With 11.5 million weekly npm downloads, support for 100+ models across 16 providers, and a growing ecosystem of Workflows and Sandbox tooling, it handles the plumbing so you can focus on your product logic.</p>
<p>This guide covers everything you need to go from zero to a deployed, production-grade AI application. No hype — just the APIs, patterns, and tradeoffs.</p>
<h2 id="what-is-the-vercel-ai-sdk-why-it-matters-in-2026">What is the Vercel AI SDK? Why It Matters in 2026</h2>
<p>The AI SDK is a TypeScript toolkit for building AI applications with streaming, structured output, tool calling, and multi-step agent patterns. It is not a model provider — it is a unified interface that sits between your application code and any LLM provider.</p>
<h3 id="115m-weekly-downloads-and-growing">11.5M Weekly Downloads and Growing</h3>
<p>The npm download numbers tell the story. AI SDK crossed 11.5 million weekly downloads in April 2026, up from roughly 4 million a year prior. The SDK has 23.7K GitHub stars and 614+ contributors. This is not a niche library. It is the dominant TypeScript AI framework.</p>
<h3 id="the-unified-typescript-ai-layer-core--ui--rsc">The Unified TypeScript AI Layer: Core + UI + RSC</h3>
<p>The SDK is organized into three layers:</p>
<table>
  <thead>
      <tr>
          <th>Layer</th>
          <th>Package</th>
          <th>Purpose</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>AI SDK Core</strong></td>
          <td><code>ai</code></td>
          <td>Server-side generation: <code>generateText</code>, <code>streamText</code>, <code>generateObject</code>, <code>streamObject</code></td>
      </tr>
      <tr>
          <td><strong>AI SDK UI</strong></td>
          <td><code>ai/react</code> (and framework equivalents)</td>
          <td>Client-side hooks: <code>useChat</code>, <code>useCompletion</code>, <code>useObject</code></td>
      </tr>
      <tr>
          <td><strong>AI SDK RSC</strong></td>
          <td><code>ai/rsc</code></td>
          <td>React Server Components integration for streaming UI from the server</td>
      </tr>
  </tbody>
</table>
<p>This separation matters. Core runs anywhere — Node, Edge, Bun, Deno. UI hooks render in the browser. RSC bridges the two in Next.js App Router. You pick the layer you need and ignore the rest.</p>
<h3 id="how-it-fits-in-the-vercel-ecosystem">How It Fits in the Vercel Ecosystem</h3>
<p>The SDK is the foundation, but the ecosystem extends further in 2026:</p>
<ul>
<li><strong>AI Gateway</strong>: One API key to access 100+ models across 16+ providers with built-in load balancing, fallbacks, and rate limiting.</li>
<li><strong>Vercel Sandbox</strong>: Secure execution environment for agent-generated code. Useful when your AI agent writes and runs code on behalf of users.</li>
<li><strong>Vercel Workflows</strong>: Durable, long-running agent execution that can suspend, resume, and survive function timeouts. New in 2026.</li>
<li><strong>AI Elements</strong>: A component library for AI-native UIs (message threads, artifact views, tool result renders). New in 2026.</li>
</ul>
<p>You do not need any of these to use AI SDK Core. They are opt-in for specific use cases.</p>
<h2 id="getting-started-installing-and-configuring-ai-sdk">Getting Started: Installing and Configuring AI SDK</h2>
<h3 id="npm-install-ai--provider-packages">npm install ai + Provider Packages</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google
</span></span></code></pre></div><p>The <code>ai</code> package contains the core functions. Provider packages (<code>@ai-sdk/openai</code>, <code>@ai-sdk/anthropic</code>, <code>@ai-sdk/google</code>) contain model definitions and provider-specific configuration. You install only the providers you use.</p>
<h3 id="setting-up-api-keys">Setting Up API Keys</h3>
<p>Store provider API keys as environment variables:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># .env.local</span>
</span></span><span style="display:flex;"><span>OPENAI_API_KEY<span style="color:#f92672">=</span>sk-...
</span></span><span style="display:flex;"><span>ANTHROPIC_API_KEY<span style="color:#f92672">=</span>sk-ant-...
</span></span><span style="display:flex;"><span>GOOGLE_GENERATIVE_AI_API_KEY<span style="color:#f92672">=</span>AIza...
</span></span></code></pre></div><p>Never commit these. In production, use your hosting platform&rsquo;s secret management.</p>
<h3 id="project-structure-for-a-nextjs-ai-app">Project Structure for a Next.js AI App</h3>
<p>A minimal structure:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 496 169"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='84' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='100' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='116' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='0' y='132' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='148' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='8' y='116' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='8' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='16' y='116' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='16' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='32' y='84' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='32' y='100' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='32' y='116' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='32' y='132' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='32' y='148' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='84' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='116' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='40' y='132' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='84' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='116' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='48' y='132' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='148' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='56' y='116' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='56' y='148' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='64' y='132' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='64' y='148' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='132' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='72' y='148' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='80' y='132' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='88' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='88' y='132' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='148' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='96' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='96' y='148' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='84' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='104' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='104' y='148' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='84' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='112' y='100' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='120' y='84' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='136' y='100' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='240' y='68' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='240' y='84' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='240' y='132' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='256' y='68' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='256' y='84' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='256' y='132' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='264' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='264' y='84' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='264' y='132' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='272' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='272' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='272' y='132' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='280' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='280' y='84' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='280' y='132' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='288' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='288' y='132' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='296' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='296' y='84' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='296' y='132' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='304' y='68' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='304' y='84' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='304' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='312' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='312' y='132' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='328' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='328' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='336' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='336' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='344' y='132' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='352' y='68' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='352' y='132' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='360' y='68' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='360' y='132' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='368' y='132' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='376' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='384' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='384' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='392' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='392' y='132' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='400' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='400' y='132' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='408' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='408' y='132' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='416' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='424' y='132' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='440' y='132' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='448' y='132' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='456' y='132' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='464' y='132' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='472' y='132' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='480' y='132' fill='currentColor' style='font-size:1em'>g</text>
</g>

    </svg>
  
</div>
<p>The <code>lib/ai.ts</code> file centralizes provider instantiation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// lib/ai.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">createOpenAI</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@ai-sdk/openai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">createAnthropic</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@ai-sdk/anthropic&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">openai</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createOpenAI</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">apiKey</span>: <span style="color:#66d9ef">process.env.OPENAI_API_KEY</span>,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">anthropic</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createAnthropic</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">apiKey</span>: <span style="color:#66d9ef">process.env.ANTHROPIC_API_KEY</span>,
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h3 id="ai-gateway-one-key-for-100-models">AI Gateway: One Key for 100+ Models</h3>
<p>If you use Vercel AI Gateway, you can replace individual provider keys with a single gateway key:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">createGateway</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@ai-sdk/gateway&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">gateway</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createGateway</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">apiKey</span>: <span style="color:#66d9ef">process.env.AI_GATEWAY_KEY</span>,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Access any model through the gateway
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">model</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">gateway</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">model2</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">gateway</span>(<span style="color:#e6db74">&#34;claude-sonnet-4-20250514&#34;</span>);
</span></span></code></pre></div><p>The Gateway handles routing, rate limiting, and fallbacks across providers. This is useful when you want model flexibility without managing multiple API keys.</p>
<h2 id="ai-sdk-core-text-generation-and-streaming">AI SDK Core: Text Generation and Streaming</h2>
<h3 id="generatetext-for-one-shot-generation">generateText() for One-Shot Generation</h3>
<p><code>generateText</code> sends a prompt and waits for the complete response. Use it when you need the full result before proceeding.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateText</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">summarize</span>(<span style="color:#a6e22e">text</span>: <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">text</span>: <span style="color:#66d9ef">summary</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`Summarize the following text in 2-3 sentences:</span><span style="color:#960050;background-color:#1e0010">\</span><span style="color:#e6db74">n</span><span style="color:#960050;background-color:#1e0010">\</span><span style="color:#e6db74">n</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">text</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">summary</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The return object includes <code>text</code>, <code>usage</code> (token counts), <code>finishReason</code>, and <code>response</code> (raw provider response).</p>
<h3 id="streamtext-for-real-time-streaming">streamText() for Real-Time Streaming</h3>
<p><code>streamText</code> returns a stream that yields tokens as they arrive. This is the foundation for chat interfaces.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">streamText</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">streamResponse</span>(<span style="color:#a6e22e">prompt</span>: <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamText</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">prompt</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Consume as a text stream
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">for</span> <span style="color:#66d9ef">await</span> (<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">chunk</span> <span style="color:#66d9ef">of</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">textStream</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">stdout</span>.<span style="color:#a6e22e">write</span>(<span style="color:#a6e22e">chunk</span>);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In a Next.js route handler, you return the stream directly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// app/api/chat/route.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">streamText</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">POST</span>(<span style="color:#a6e22e">req</span>: <span style="color:#66d9ef">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">messages</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">json</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamText</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">messages</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">toDataStreamResponse</span>();
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>toDataStreamResponse()</code> converts the stream into the AI SDK data stream protocol — the format that client-side hooks expect.</p>
<h3 id="provider-switching-with-one-line-of-code">Provider Switching with One Line of Code</h3>
<p>Because models are just configuration objects, switching providers requires changing one argument:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// OpenAI
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Explain quantum computing&#34;</span>,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Switch to Anthropic — only the model line changes
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">anthropic</span>(<span style="color:#e6db74">&#34;claude-sonnet-4-20250514&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Explain quantum computing&#34;</span>,
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>No other code changes. The prompt format, streaming mechanism, and response handling remain identical.</p>
<h3 id="built-in-fallbacks-and-retry-logic">Built-in Fallbacks and Retry Logic</h3>
<p>When a provider fails or rate-limits, you can fall back to an alternative model:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateText</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">anthropic</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">text</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fallbackModels</span><span style="color:#f92672">:</span> [<span style="color:#a6e22e">anthropic</span>(<span style="color:#e6db74">&#34;claude-sonnet-4-20250514&#34;</span>)],
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Explain the halting problem&#34;</span>,
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>If the primary model fails, the SDK automatically retries with the first fallback. You can configure <code>maxRetries</code> and <code>abortSignal</code> for finer control.</p>
<h2 id="structured-output-with-zod-schemas">Structured Output with Zod Schemas</h2>
<p>Raw LLM text is fine for chat. For application logic, you need structured data. The AI SDK integrates with Zod to enforce type-safe schemas on model output.</p>
<h3 id="generateobject-for-type-safe-json-responses">generateObject() for Type-Safe JSON Responses</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateObject</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">RecipeSchema</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ingredients</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">item</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">amount</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>  })),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">steps</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">string</span>()),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">cookTimeMinutes</span>: <span style="color:#66d9ef">z.number</span>(),
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getRecipe</span>(<span style="color:#a6e22e">dish</span>: <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#66d9ef">object</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateObject</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">schema</span>: <span style="color:#66d9ef">RecipeSchema</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`Generate a recipe for </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">dish</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// object is fully typed as z.infer&lt;typeof RecipeSchema&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">object</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The return value <code>object</code> has the TypeScript type inferred from the Zod schema. If the model produces output that does not conform, the SDK throws a <code>AI_TypeError</code>.</p>
<h3 id="streamobject-for-streaming-structured-data">streamObject() for Streaming Structured Data</h3>
<p>When generating large structured objects, <code>streamObject</code> returns partial results as they arrive:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">streamObject</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamObject</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">schema</span>: <span style="color:#66d9ef">z.object</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">analysis</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">score</span>: <span style="color:#66d9ef">z.number</span>(),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">recommendations</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">string</span>()),
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Analyze the following code for security vulnerabilities: ...&#34;</span>,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#66d9ef">await</span> (<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">partialObject</span> <span style="color:#66d9ef">of</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">partialObjectStream</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// partialObject contains the fields that have been generated so far
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">partialObject</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Use <code>partialObjectStream</code> for real-time UI updates as the model fills in each field.</p>
<h3 id="zod-schema-definitions-and-validation">Zod Schema Definitions and Validation</h3>
<p>The SDK supports most Zod types: strings, numbers, booleans, arrays, objects, enums, unions, and optional fields. It does not support transforms or refinements — those apply after the model output is validated.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// Supported
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">schema</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">status</span>: <span style="color:#66d9ef">z.enum</span>([<span style="color:#e6db74">&#34;active&#34;</span>, <span style="color:#e6db74">&#34;inactive&#34;</span>, <span style="color:#e6db74">&#34;pending&#34;</span>]),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">tags</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">string</span>()),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">metadata</span>: <span style="color:#66d9ef">z.record</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">string</span>(), <span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">any</span>()).<span style="color:#a6e22e">optional</span>(),
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Not supported in schema (apply after)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">schema</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">email</span>: <span style="color:#66d9ef">z.string</span>(), <span style="color:#75715e">// validate format after with .transform()
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">score</span>: <span style="color:#66d9ef">z.number</span>().<span style="color:#a6e22e">min</span>(<span style="color:#ae81ff">0</span>).<span style="color:#a6e22e">max</span>(<span style="color:#ae81ff">100</span>), <span style="color:#75715e">// .min/.max work but are hints, not hard guards
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>});
</span></span></code></pre></div><h3 id="practical-example-extracting-structured-data-from-unstructured-text">Practical Example: Extracting Structured Data from Unstructured Text</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateObject</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ContactSchema</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">email</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">optional</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">phone</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">optional</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">company</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">optional</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">title</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">optional</span>(),
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">extractContacts</span>(<span style="color:#a6e22e">emailText</span>: <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#66d9ef">object</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">contacts</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateObject</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">schema</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">ContactSchema</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`Extract all people and their contact information from this email:</span><span style="color:#960050;background-color:#1e0010">\</span><span style="color:#e6db74">n</span><span style="color:#960050;background-color:#1e0010">\</span><span style="color:#e6db74">n</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">emailText</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">contacts</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Input: &#34;Hi, I&#39;m Sarah Chen (VP Eng at Acme, sarah@acme.co). 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//         CC&#39;d: John Park (john.park@beta.dev)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// Output: [
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//   { name: &#34;Sarah Chen&#34;, email: &#34;sarah@acme.co&#34;, company: &#34;Acme&#34;, title: &#34;VP Eng&#34; },
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//   { name: &#34;John Park&#34;, email: &#34;john.park@beta.dev&#34;, company: undefined, title: undefined }
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// ]
</span></span></span></code></pre></div><p>No regex. No parsing. The model extracts structured data from freeform text, and the Zod schema guarantees the output shape.</p>
<h2 id="building-chat-uis-with-ai-sdk-ui-hooks">Building Chat UIs with AI SDK UI Hooks</h2>
<h3 id="usechat-for-chat-interfaces">useChat() for Chat Interfaces</h3>
<p><code>useChat</code> manages conversation state, sends messages to your API route, and streams responses back to the UI:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// app/page.tsx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#e6db74">&#34;use client&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useChat</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai/react&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">ChatPage() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">messages</span>, <span style="color:#a6e22e">input</span>, <span style="color:#a6e22e">handleInputChange</span>, <span style="color:#a6e22e">handleSubmit</span>, <span style="color:#a6e22e">isLoading</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">useChat</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">api</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;/api/chat&#34;</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;max-w-2xl mx-auto p-4&#34;</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;space-y-4 mb-4&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        {<span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">m</span>) <span style="color:#f92672">=&gt;</span> (
</span></span><span style="display:flex;"><span>          &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">id</span>} <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;user&#34;</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#34;text-right&#34;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;text-left&#34;</span>}&gt;
</span></span><span style="display:flex;"><span>            &lt;<span style="color:#f92672">div</span>
</span></span><span style="display:flex;"><span>              <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span>{<span style="color:#e6db74">`inline-block rounded-lg px-4 py-2 </span><span style="color:#e6db74">${</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;user&#34;</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#34;bg-blue-600 text-white&#34;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;bg-gray-100 text-gray-900&#34;</span>
</span></span><span style="display:flex;"><span>              <span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>}
</span></span><span style="display:flex;"><span>            &gt;
</span></span><span style="display:flex;"><span>              {<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">content</span>}
</span></span><span style="display:flex;"><span>            &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>          &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>        ))}
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">onSubmit</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleSubmit</span>} <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;flex gap-2&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">input</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">input</span>}
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">onChange</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleInputChange</span>}
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">placeholder</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Ask something...&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;flex-1 rounded border px-3 py-2&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">disabled</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">isLoading</span>}
</span></span><span style="display:flex;"><span>        /&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;submit&#34;</span> <span style="color:#a6e22e">disabled</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">isLoading</span>} <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;rounded bg-blue-600 px-4 py-2 text-white&#34;</span>&gt;
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">Send</span>
</span></span><span style="display:flex;"><span>        &lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">form</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The hook handles message history, loading state, error state, and automatic scrolling. The server route from the previous section (<code>/api/chat</code>) handles the model call and streaming.</p>
<h3 id="usecompletion-for-auto-complete">useCompletion() for Auto-Complete</h3>
<p><code>useCompletion</code> is for single-turn generation patterns — autocomplete, suggestions, summarization — where you do not need conversation history:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#e6db74">&#34;use client&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useCompletion</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai/react&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">Summarizer() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">completion</span>, <span style="color:#a6e22e">input</span>, <span style="color:#a6e22e">handleInputChange</span>, <span style="color:#a6e22e">handleSubmit</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">useCompletion</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">api</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;/api/completion&#34;</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">onSubmit</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleSubmit</span>}&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">textarea</span> <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">input</span>} <span style="color:#a6e22e">onChange</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleInputChange</span>} <span style="color:#a6e22e">placeholder</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Paste text to summarize&#34;</span> /&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;submit&#34;</span>&gt;<span style="color:#a6e22e">Summarize</span>&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#a6e22e">completion</span> <span style="color:#f92672">&amp;&amp;</span> &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;mt-4&#34;</span>&gt;{<span style="color:#a6e22e">completion</span>}&lt;/<span style="color:#f92672">div</span>&gt;}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">form</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="useobject-for-streaming-structured-responses">useObject() for Streaming Structured Responses</h3>
<p>When your API route returns a streamed object (via <code>streamObject</code>), use <code>useObject</code> on the client:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#e6db74">&#34;use client&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useObject</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai/react&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">AnalysisSchema</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">summary</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">sentiment</span>: <span style="color:#66d9ef">z.enum</span>([<span style="color:#e6db74">&#34;positive&#34;</span>, <span style="color:#e6db74">&#34;negative&#34;</span>, <span style="color:#e6db74">&#34;neutral&#34;</span>]),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">keywords</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">string</span>()),
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">Analyzer() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#66d9ef">object</span>, <span style="color:#a6e22e">submit</span>, <span style="color:#a6e22e">isLoading</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">useObject</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">api</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;/api/analyze&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">schema</span>: <span style="color:#66d9ef">AnalysisSchema</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">onClick</span><span style="color:#f92672">=</span>{() <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">submit</span>({ <span style="color:#a6e22e">text</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Product is great but shipping was slow&#34;</span> })} <span style="color:#a6e22e">disabled</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">isLoading</span>}&gt;
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Analyze</span>
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#66d9ef">object</span> <span style="color:#f92672">&amp;&amp;</span> (
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;mt-4 space-y-2&#34;</span>&gt;
</span></span><span style="display:flex;"><span>          &lt;<span style="color:#f92672">p</span>&gt;&lt;<span style="color:#f92672">strong</span>&gt;<span style="color:#a6e22e">Summary</span><span style="color:#f92672">:</span>&lt;/<span style="color:#f92672">strong</span>&gt; {<span style="color:#66d9ef">object</span>.<span style="color:#a6e22e">summary</span>}&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>          &lt;<span style="color:#f92672">p</span>&gt;&lt;<span style="color:#f92672">strong</span>&gt;<span style="color:#a6e22e">Sentiment</span><span style="color:#f92672">:</span>&lt;/<span style="color:#f92672">strong</span>&gt; {<span style="color:#66d9ef">object</span>.<span style="color:#a6e22e">sentiment</span>}&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>          &lt;<span style="color:#f92672">p</span>&gt;&lt;<span style="color:#f92672">strong</span>&gt;<span style="color:#a6e22e">Keywords</span><span style="color:#f92672">:</span>&lt;/<span style="color:#f92672">strong</span>&gt; {<span style="color:#66d9ef">object</span>.<span style="color:#a6e22e">keywords</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">join</span>(<span style="color:#e6db74">&#34;, &#34;</span>)}&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>        &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      )}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="framework-support">Framework Support</h3>
<p>The UI hooks are not React-only. AI SDK ships framework-specific packages:</p>
<table>
  <thead>
      <tr>
          <th>Framework</th>
          <th>Package</th>
          <th>Hooks</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>React</td>
          <td><code>ai/react</code></td>
          <td><code>useChat</code>, <code>useCompletion</code>, <code>useObject</code></td>
      </tr>
      <tr>
          <td>Vue</td>
          <td><code>ai/vue</code></td>
          <td><code>useChat</code>, <code>useCompletion</code>, <code>useObject</code></td>
      </tr>
      <tr>
          <td>Svelte</td>
          <td><code>ai/svelte</code></td>
          <td><code>useChat</code>, <code>useCompletion</code>, <code>useObject</code></td>
      </tr>
      <tr>
          <td>Solid</td>
          <td><code>ai/solid</code></td>
          <td><code>useChat</code>, <code>useCompletion</code>, <code>useObject</code></td>
      </tr>
  </tbody>
</table>
<p>The server-side <code>streamText</code> and <code>streamObject</code> functions are framework-agnostic. Only the client hooks differ.</p>
<h2 id="tool-calling-giving-your-ai-agent-superpowers">Tool Calling: Giving Your AI Agent Superpowers</h2>
<p>Tool calling lets the model invoke functions you define. Instead of just generating text, the model can trigger actions — fetch data, run calculations, query databases — then incorporate the results into its response.</p>
<h3 id="defining-tools-with-execute-functions">Defining Tools with Execute Functions</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateText</span>, <span style="color:#a6e22e">tool</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">weather</span>: <span style="color:#66d9ef">tool</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Get current weather for a location&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">parameters</span>: <span style="color:#66d9ef">z.object</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">city</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">unit</span>: <span style="color:#66d9ef">z.enum</span>([<span style="color:#e6db74">&#34;celsius&#34;</span>, <span style="color:#e6db74">&#34;fahrenheit&#34;</span>]).<span style="color:#a6e22e">optional</span>(),
</span></span><span style="display:flex;"><span>      }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">city</span>, <span style="color:#a6e22e">unit</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;celsius&#34;</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">`https://api.weather.example/current?city=</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">city</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&amp;unit=</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">unit</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>();
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>    }),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">calculator</span>: <span style="color:#66d9ef">tool</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Evaluate a mathematical expression&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">parameters</span>: <span style="color:#66d9ef">z.object</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">expression</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>      }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">expression</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Simple eval for demo — use a safe math parser in production
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> Function(<span style="color:#e6db74">`&#34;use strict&#34;; return (</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">expression</span><span style="color:#e6db74">}</span><span style="color:#e6db74">)`</span>)();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> { <span style="color:#a6e22e">result</span> };
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>    }),
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;What&#39;s the weather in Seoul, and what is 15% of 340?&#34;</span>,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">text</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// &#34;The current weather in Seoul is 12°C with partly cloudy skies.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//  15% of 340 is 51.&#34;
</span></span></span></code></pre></div><p>The model decides which tools to call based on the user&rsquo;s prompt. You do not hardcode the invocation logic.</p>
<h3 id="multi-step-agent-loops-with-maxsteps">Multi-Step Agent Loops with maxSteps</h3>
<p>A single model call with tools might not finish the job. The model might need to call a tool, read the result, then decide to call another tool. <code>maxSteps</code> enables this multi-step loop:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateText</span>, <span style="color:#a6e22e">tool</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">maxSteps</span>: <span style="color:#66d9ef">5</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">searchDatabase</span>: <span style="color:#66d9ef">tool</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Search the product database&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">parameters</span>: <span style="color:#66d9ef">z.object</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">query</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">category</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">optional</span>(),
</span></span><span style="display:flex;"><span>      }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">query</span>, <span style="color:#a6e22e">category</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Simulated database query
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">query</span>(<span style="color:#a6e22e">query</span>, { <span style="color:#a6e22e">category</span> });
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>    }),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">checkInventory</span>: <span style="color:#66d9ef">tool</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Check inventory for a product ID&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">parameters</span>: <span style="color:#66d9ef">z.object</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">productId</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>      }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">productId</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">inventoryAPI</span>.<span style="color:#a6e22e">check</span>(<span style="color:#a6e22e">productId</span>);
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>    }),
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Find wireless headphones under $100 and check if the top result is in stock&#34;</span>,
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>With <code>maxSteps: 5</code>, the model can make up to 5 tool calls in sequence. After each tool result, it decides whether to call another tool or produce a final text response.</p>
<h3 id="tool-result-streaming-to-the-client">Tool Result Streaming to the Client</h3>
<p>When using <code>streamText</code> with tools, tool invocations and results are included in the data stream. On the client, <code>useChat</code> exposes tool state:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#e6db74">&#34;use client&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useChat</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai/react&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">ChatWithTools() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">messages</span>, <span style="color:#a6e22e">input</span>, <span style="color:#a6e22e">handleInputChange</span>, <span style="color:#a6e22e">handleSubmit</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">useChat</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">api</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;/api/chat&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">maxSteps</span>: <span style="color:#66d9ef">3</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">m</span>) <span style="color:#f92672">=&gt;</span> (
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">id</span>}&gt;
</span></span><span style="display:flex;"><span>          {<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;assistant&#34;</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">toolInvocations</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">invocation</span>) <span style="color:#f92672">=&gt;</span> (
</span></span><span style="display:flex;"><span>            &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">invocation</span>.<span style="color:#a6e22e">toolCallId</span>} <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;bg-yellow-50 p-2 rounded text-sm&#34;</span>&gt;
</span></span><span style="display:flex;"><span>              {<span style="color:#a6e22e">invocation</span>.<span style="color:#a6e22e">state</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;call&#34;</span> <span style="color:#f92672">&amp;&amp;</span> &lt;<span style="color:#f92672">span</span>&gt;<span style="color:#a6e22e">Calling</span> {<span style="color:#a6e22e">invocation</span>.<span style="color:#a6e22e">toolName</span>}...&lt;/<span style="color:#f92672">span</span>&gt;}
</span></span><span style="display:flex;"><span>              {<span style="color:#a6e22e">invocation</span>.<span style="color:#a6e22e">state</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;result&#34;</span> <span style="color:#f92672">&amp;&amp;</span> (
</span></span><span style="display:flex;"><span>                &lt;<span style="color:#f92672">span</span>&gt;<span style="color:#a6e22e">Result</span><span style="color:#f92672">:</span> {<span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">invocation</span>.<span style="color:#a6e22e">result</span>)}&lt;/<span style="color:#f92672">span</span>&gt;
</span></span><span style="display:flex;"><span>              )}
</span></span><span style="display:flex;"><span>            &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>          ))}
</span></span><span style="display:flex;"><span>          {<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">content</span> <span style="color:#f92672">&amp;&amp;</span> &lt;<span style="color:#f92672">div</span>&gt;{<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">content</span>}&lt;/<span style="color:#f92672">div</span>&gt;}
</span></span><span style="display:flex;"><span>        &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      ))}
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">onSubmit</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleSubmit</span>}&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">input</span>} <span style="color:#a6e22e">onChange</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleInputChange</span>} /&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;submit&#34;</span>&gt;<span style="color:#a6e22e">Send</span>&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">form</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="building-ai-agents-with-multi-step-reasoning">Building AI Agents with Multi-Step Reasoning</h2>
<h3 id="the-agent-loop-pattern-with-tool-calling">The Agent Loop Pattern with Tool Calling</h3>
<p>An agent is a loop: the model reasons, calls tools, observes results, and repeats until the task is done. The AI SDK handles the loop via <code>maxSteps</code>. Your job is to define the tools and the system prompt.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateText</span>, <span style="color:#a6e22e">tool</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">researchAgent</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">query</span>: <span style="color:#66d9ef">string</span>) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">maxSteps</span>: <span style="color:#66d9ef">10</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">system</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`You are a research assistant. Use the available tools to find, verify, and summarize information.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Always cite your sources. If you cannot find a definitive answer, say so explicitly.`</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">webSearch</span>: <span style="color:#66d9ef">tool</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Search the web for information&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">parameters</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">query</span>: <span style="color:#66d9ef">z.string</span>() }),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">query</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">results</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">searchAPI</span>.<span style="color:#a6e22e">search</span>(<span style="color:#a6e22e">query</span>);
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">results</span>.<span style="color:#a6e22e">slice</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">5</span>);
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>      }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">getPageContent</span>: <span style="color:#66d9ef">tool</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Fetch the content of a web page&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">parameters</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">url</span>: <span style="color:#66d9ef">z.string</span>() }),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">url</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">resp</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">url</span>);
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">text</span>().<span style="color:#a6e22e">slice</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">5000</span>); <span style="color:#75715e">// Truncate for context limits
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        },
</span></span><span style="display:flex;"><span>      }),
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">prompt</span>: <span style="color:#66d9ef">query</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">text</span>;
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><h3 id="memory-and-conversation-history-management">Memory and Conversation History Management</h3>
<p>For multi-turn agents, you pass the full message history on each request:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// app/api/agent/route.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">streamText</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">POST</span>(<span style="color:#a6e22e">req</span>: <span style="color:#66d9ef">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">messages</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">json</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamText</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">maxSteps</span>: <span style="color:#66d9ef">5</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">system</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;You are a helpful coding assistant with access to a codebase search tool.&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">messages</span>, <span style="color:#75715e">// Full conversation history
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">searchCode</span>: <span style="color:#66d9ef">tool</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Search the codebase for code matching a query&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">parameters</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">query</span>: <span style="color:#66d9ef">z.string</span>() }),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">query</span> }) <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">codebaseIndex</span>.<span style="color:#a6e22e">search</span>(<span style="color:#a6e22e">query</span>),
</span></span><span style="display:flex;"><span>      }),
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">toDataStreamResponse</span>();
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Manage history size to stay within context windows. A common pattern: keep the system prompt + the last N messages, or implement a summarization step for older messages.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">truncateMessages</span>(<span style="color:#a6e22e">messages</span>: <span style="color:#66d9ef">Message</span>[], <span style="color:#a6e22e">maxMessages</span>: <span style="color:#66d9ef">number</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">20</span>)<span style="color:#f92672">:</span> <span style="color:#a6e22e">Message</span>[] {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">&lt;=</span> <span style="color:#a6e22e">maxMessages</span>) <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">messages</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Keep system message (if any) + last N messages
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">systemMessages</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">filter</span>((<span style="color:#a6e22e">m</span>) <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;system&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">nonSystem</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">filter</span>((<span style="color:#a6e22e">m</span>) <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#34;system&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> [...<span style="color:#a6e22e">systemMessages</span>, ...<span style="color:#a6e22e">nonSystem</span>.<span style="color:#a6e22e">slice</span>(<span style="color:#f92672">-</span><span style="color:#a6e22e">maxMessages</span>)];
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="rag-integration-pattern">RAG Integration Pattern</h3>
<p>Retrieval-Augmented Generation injects relevant context into the prompt before the model responds. With tool calling, the model can decide when to retrieve:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateText</span>, <span style="color:#a6e22e">tool</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ragAgent</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">query</span>: <span style="color:#66d9ef">string</span>) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">maxSteps</span>: <span style="color:#66d9ef">3</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">system</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Answer questions based on the documentation. Use the search tool to find relevant docs before answering.&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">searchDocs</span>: <span style="color:#66d9ef">tool</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Search the product documentation&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">parameters</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">query</span>: <span style="color:#66d9ef">z.string</span>() }),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">query</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">results</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">vectorStore</span>.<span style="color:#a6e22e">search</span>(<span style="color:#a6e22e">query</span>, { <span style="color:#a6e22e">topK</span>: <span style="color:#66d9ef">5</span> });
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">results</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">r</span>) <span style="color:#f92672">=&gt;</span> ({ <span style="color:#a6e22e">content</span>: <span style="color:#66d9ef">r.text</span>, <span style="color:#a6e22e">source</span>: <span style="color:#66d9ef">r.metadata.source</span> }));
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>      }),
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">prompt</span>: <span style="color:#66d9ef">query</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">text</span>;
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>The alternative is &ldquo;always-retrieve&rdquo; — fetch context on every request and inject it into the system prompt. The tool-calling approach is more efficient because the model retrieves only when needed.</p>
<h2 id="vercel-workflows-long-running-agents-that-survive">Vercel Workflows: Long-Running Agents That Survive</h2>
<h3 id="what-are-vercel-workflows-and-when-to-use-them">What Are Vercel Workflows and When to Use Them</h3>
<p>Serverless functions have timeout limits — 10 seconds on Vercel Hobby, 300 seconds on Pro. An agent that needs to call multiple tools, wait for external APIs, or generate long content can easily exceed these limits.</p>
<p>Vercel Workflows solve this by providing durable execution. A workflow can:</p>
<ul>
<li><strong>Suspend</strong> execution and resume when an event occurs (user approval, external API callback)</li>
<li><strong>Survive</strong> function restarts — state is persisted, not lost</li>
<li><strong>Run</strong> for minutes or hours, not seconds</li>
</ul>
<p>Use Workflows when your agent needs to:</p>
<ul>
<li>Wait for human approval before acting</li>
<li>Chain many tool calls that exceed function timeout</li>
<li>Process long-running tasks (content generation pipelines, data analysis)</li>
</ul>
<h3 id="suspend-resume-and-survive-timeouts">Suspend, Resume, and Survive Timeouts</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">workflow</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@vercel/orchestration&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateText</span>, <span style="color:#a6e22e">tool</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">contentPipeline</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">workflow</span>.<span style="color:#a6e22e">define</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;content-generation&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">timeout</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;1h&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">async</span> <span style="color:#a6e22e">run</span>(<span style="color:#a6e22e">ctx</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Step 1: Generate outline
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> { <span style="color:#66d9ef">object</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">outline</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateObject</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">schema</span>: <span style="color:#66d9ef">z.object</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">title</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">sections</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">heading</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">keyPoints</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">string</span>()),
</span></span><span style="display:flex;"><span>        })),
</span></span><span style="display:flex;"><span>      }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">prompt</span>: <span style="color:#66d9ef">ctx.request.topic</span>,
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Step 2: Suspend for human review
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">approval</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">waitForEvent</span>(<span style="color:#e6db74">&#34;approval&#34;</span>, {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">timeout</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;24h&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">data</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">outline</span> },
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">approval</span>.<span style="color:#a6e22e">approved</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> { <span style="color:#a6e22e">status</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;rejected&#34;</span>, <span style="color:#a6e22e">reason</span>: <span style="color:#66d9ef">approval.reason</span> };
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Step 3: Generate full content from approved outline
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">sections</span> <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">section</span> <span style="color:#66d9ef">of</span> <span style="color:#a6e22e">outline</span>.<span style="color:#a6e22e">sections</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">text</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`Write a detailed section based on: </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">section</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>,
</span></span><span style="display:flex;"><span>      });
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">sections</span>.<span style="color:#a6e22e">push</span>({ <span style="color:#a6e22e">heading</span>: <span style="color:#66d9ef">section.heading</span>, <span style="color:#a6e22e">content</span>: <span style="color:#66d9ef">text</span> });
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> { <span style="color:#a6e22e">status</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;complete&#34;</span>, <span style="color:#a6e22e">content</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">title</span>: <span style="color:#66d9ef">outline.title</span>, <span style="color:#a6e22e">sections</span> } };
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h3 id="building-durable-ai-agents">Building Durable AI Agents</h3>
<p>The key difference between a standard <code>maxSteps</code> agent and a workflow agent is durability. A <code>maxSteps</code> agent runs in a single function invocation. If the function is interrupted, the agent loses all progress. A workflow agent persists state at each step. If the function restarts, the workflow picks up where it left off.</p>
<p>This makes Workflows appropriate for:</p>
<ul>
<li>Content generation pipelines with human review steps</li>
<li>Data analysis agents that run queries over minutes</li>
<li>Multi-agent systems where one agent delegates to another and waits for results</li>
<li>Any agent that interacts with asynchronous external systems</li>
</ul>
<h2 id="production-deployment-and-scaling">Production Deployment and Scaling</h2>
<h3 id="deploying-to-vercel-vs-self-hosting">Deploying to Vercel vs Self-Hosting</h3>
<table>
  <thead>
      <tr>
          <th>Aspect</th>
          <th>Vercel</th>
          <th>Self-Hosted (Node/Docker)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Streaming</td>
          <td>Native (Edge + Node)</td>
          <td>Requires proper streaming server setup</td>
      </tr>
      <tr>
          <td>Cold starts</td>
          <td>Edge: ~50ms, Serverless: ~200ms</td>
          <td>Depends on your infrastructure</td>
      </tr>
      <tr>
          <td>Timeouts</td>
          <td>10s (Hobby), 300s (Pro), Workflows for longer</td>
          <td>No limit (configure yourself)</td>
      </tr>
      <tr>
          <td>AI Gateway</td>
          <td>Built-in</td>
          <td>Self-host or skip</td>
      </tr>
      <tr>
          <td>Sandbox</td>
          <td>Built-in</td>
          <td>Bring your own sandbox</td>
      </tr>
      <tr>
          <td>Scaling</td>
          <td>Automatic</td>
          <td>Manual or Kubernetes</td>
      </tr>
  </tbody>
</table>
<p>For many teams, Vercel is the fastest path to production. The Edge runtime handles streaming with minimal latency. Self-hosting gives you more control over timeouts and compute resources.</p>
<h3 id="edge-runtime-considerations">Edge Runtime Considerations</h3>
<p>AI SDK runs on the Edge runtime. This means:</p>
<ul>
<li>No Node.js-specific APIs (fs, child_process, native modules) in edge functions</li>
<li>Tool <code>execute</code> functions that depend on Node APIs must run on the Node runtime</li>
<li>You can mix: stream on Edge, execute tools on Node</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// If your tools need Node.js APIs, use the Node runtime
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">runtime</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;nodejs&#34;</span>; <span style="color:#75715e">// not &#34;edge&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">POST</span>(<span style="color:#a6e22e">req</span>: <span style="color:#66d9ef">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// This function can use fs, child_process, etc.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamText</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">messages</span>: <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">json</span>(),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> { <span style="color:#75715e">/* node-dependent tools */</span> },
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">toDataStreamResponse</span>();
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="rate-limiting-and-cost-management">Rate Limiting and Cost Management</h3>
<p>LLM API calls cost money. Without limits, a single user can rack up significant charges. Implement rate limiting at the API route level:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Ratelimit</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@upstash/ratelimit&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Redis</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@upstash/redis&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ratelimit</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Ratelimit</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">redis</span>: <span style="color:#66d9ef">Redis.fromEnv</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">limiter</span>: <span style="color:#66d9ef">Ratelimit.slidingWindow</span>(<span style="color:#ae81ff">10</span>, <span style="color:#e6db74">&#34;1m&#34;</span>), <span style="color:#75715e">// 10 requests per minute
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">POST</span>(<span style="color:#a6e22e">req</span>: <span style="color:#66d9ef">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ip</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">headers</span>.<span style="color:#66d9ef">get</span>(<span style="color:#e6db74">&#34;x-forwarded-for&#34;</span>) <span style="color:#f92672">??</span> <span style="color:#e6db74">&#34;anonymous&#34;</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">success</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">ratelimit</span>.<span style="color:#a6e22e">limit</span>(<span style="color:#a6e22e">ip</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">success</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Response</span>(<span style="color:#e6db74">&#34;Rate limit exceeded&#34;</span>, { <span style="color:#a6e22e">status</span>: <span style="color:#66d9ef">429</span> });
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ... AI SDK call
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>Track token usage from the result object to monitor costs:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">usage</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// { promptTokens: 124, completionTokens: 89, totalTokens: 213 }
</span></span></span></code></pre></div><p>Log this to your observability system. Vercel logs capture request metadata automatically. For custom tracking, send <code>result.usage</code> to your analytics endpoint.</p>
<h3 id="monitoring-with-vercel-logs-and-observability">Monitoring with Vercel Logs and Observability</h3>
<p>The AI SDK supports OpenTelemetry for distributed tracing. In Vercel, request-level logs are available in the dashboard. For deeper observability:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateText</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;ai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/lib/ai&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">experimental_telemetry</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">isEnabled</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">functionId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;chat-completion&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">metadata</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">userId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;user-123&#34;</span> },
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>This exports spans and metrics to your OpenTelemetry collector. You can trace the full lifecycle: request received, model called, tokens streamed, response completed.</p>
<h2 id="ai-sdk-vs-langchain-vs-mastra-framework-comparison">AI SDK vs LangChain vs Mastra: Framework Comparison</h2>
<p>Three frameworks dominate TypeScript AI development in 2026. Here is how they compare.</p>
<h3 id="feature-matrix">Feature Matrix</h3>
<table>
  <thead>
      <tr>
          <th>Feature</th>
          <th>AI SDK</th>
          <th>LangChain.ts</th>
          <th>Mastra</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Streaming</td>
          <td>First-class, built-in</td>
          <td>Supported, requires config</td>
          <td>Built-in</td>
      </tr>
      <tr>
          <td>Tool calling</td>
          <td>Native, Zod-based</td>
          <td>Native, Zod-based</td>
          <td>Native, Zod-based</td>
      </tr>
      <tr>
          <td>Structured output</td>
          <td>generateObject/streamObject</td>
          <td>StructuredOutputParser</td>
          <td>generateObject</td>
      </tr>
      <tr>
          <td>Multi-step agents</td>
          <td>maxSteps</td>
          <td>AgentExecutor</td>
          <td>Agent loops</td>
      </tr>
      <tr>
          <td>UI hooks</td>
          <td>Built-in (React, Vue, Svelte, Solid)</td>
          <td>None (bring your own)</td>
          <td>Built-in (React)</td>
      </tr>
      <tr>
          <td>Provider count</td>
          <td>16+ / 100+ models</td>
          <td>30+ providers</td>
          <td>10+ providers</td>
      </tr>
      <tr>
          <td>Durable workflows</td>
          <td>Vercel Workflows integration</td>
          <td>LangGraph for state machines</td>
          <td>Built-in workflow engine</td>
      </tr>
      <tr>
          <td>Bundle size</td>
          <td>~15KB core</td>
          <td>~200KB+</td>
          <td>~25KB core</td>
      </tr>
      <tr>
          <td>Edge runtime</td>
          <td>Yes</td>
          <td>Partial</td>
          <td>Yes</td>
      </tr>
      <tr>
          <td>RSC support</td>
          <td>Built-in</td>
          <td>None</td>
          <td>Partial</td>
      </tr>
  </tbody>
</table>
<h3 id="performance-bundle-size-and-latency">Performance: Bundle Size and Latency</h3>
<p>AI SDK is significantly lighter than LangChain. The core <code>ai</code> package is roughly 15KB minified. LangChain.ts with its dependency chain can exceed 200KB. This matters for client-side imports and cold start times.</p>
<p>For latency, the overhead added by each framework is negligible compared to model inference time (hundreds of milliseconds to seconds). The real latency difference comes from streaming architecture. AI SDK streams are optimized for Edge runtime with minimal buffering, which translates to lower time-to-first-token on Vercel.</p>
<h3 id="when-to-choose-each-framework">When to Choose Each Framework</h3>
<table>
  <thead>
      <tr>
          <th>Choose AI SDK when</th>
          <th>Choose LangChain when</th>
          <th>Choose Mastra when</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Building on Next.js/Vercel</td>
          <td>You need LangGraph&rsquo;s state machine model</td>
          <td>You want an all-in-one agent framework</td>
      </tr>
      <tr>
          <td>You want minimal bundle size</td>
          <td>You&rsquo;re porting from LangChain Python</td>
          <td>You need built-in RAG pipelines</td>
      </tr>
      <tr>
          <td>Streaming is a priority</td>
          <td>You need 30+ niche providers</td>
          <td>You prefer convention over configuration</td>
      </tr>
      <tr>
          <td>You want UI hooks out of the box</td>
          <td>Your team already uses LangChain</td>
          <td>You want built-in MCP server support</td>
      </tr>
      <tr>
          <td>Edge/runtime compatibility matters</td>
          <td>You need LangSmith for observability</td>
          <td>You want a hosted agent dashboard</td>
      </tr>
  </tbody>
</table>
<p>The honest answer: for most TypeScript AI apps in 2026, AI SDK is the practical default. LangChain is the right choice for teams that need its larger ecosystem or are migrating from Python. Mastra is worth evaluating if you want a more opinionated framework with built-in infrastructure.</p>
<h2 id="conclusion-and-resources">Conclusion and Resources</h2>
<h3 id="key-takeaways">Key Takeaways</h3>
<ol>
<li><strong>AI SDK Core</strong> handles text generation, streaming, structured output, and tool calling with a provider-agnostic API. Switch models by changing one argument.</li>
<li><strong>Structured output</strong> with <code>generateObject</code> and Zod schemas gives you type-safe JSON from LLM responses — no more parsing raw text.</li>
<li><strong>UI hooks</strong> (<code>useChat</code>, <code>useCompletion</code>, <code>useObject</code>) handle client-side state, streaming, and rendering across React, Vue, Svelte, and Solid.</li>
<li><strong>Tool calling + maxSteps</strong> turns a text generator into a multi-step agent. The model decides when and which tools to call.</li>
<li><strong>Vercel Workflows</strong> add durability for long-running agents that exceed function timeouts or need human-in-the-loop patterns.</li>
<li><strong>Production readiness</strong> means rate limiting, token usage tracking, and runtime selection (Edge vs Node). The SDK gives you the hooks; you add the guardrails.</li>
</ol>
<h3 id="official-docs-templates-and-cookbooks">Official Docs, Templates, and Cookbooks</h3>
<ul>
<li><a href="https://ai-sdk.dev/docs">AI SDK Documentation</a> — the canonical reference for all APIs</li>
<li><a href="https://github.com/vercel/ai">AI SDK GitHub</a> — source, issues, and releases</li>
<li><a href="https://vercel.com/templates/next.js/nextjs-ai-chatbot">Next.js AI Chatbot Template</a> — production-starter with auth, persistence, and multi-model support</li>
<li><a href="https://ai-sdk.dev/docs/cookbook">AI SDK Cookbooks</a> — patterns for RAG, agents, and structured output</li>
</ul>
<h3 id="community">Community</h3>
<ul>
<li><a href="https://github.com/vercel/ai/discussions">GitHub Discussions</a> — questions, feature requests, and architecture discussions</li>
<li><a href="https://vercel.com/discord">Vercel Discord</a> — real-time help from the community and maintainers</li>
</ul>
<h3 id="whats-next">What&rsquo;s Next</h3>
<p>Two developments to watch in the second half of 2026:</p>
<ul>
<li><strong>AI Elements</strong>: The new component library for AI-native UIs (message threads, tool result renderers, artifact views). Currently in beta, expected to reach stable by Q3.</li>
<li><strong>Workflows evolution</strong>: Vercel Workflows are new. Expect more patterns, better debugging, and tighter AI SDK integration as the API matures.</li>
</ul>
<p>The AI SDK has reached the point where the core APIs are stable and production-proven. The ecosystem around it — Workflows, Sandbox, AI Elements — is still evolving rapidly. Start with Core, add pieces as your use case demands them.</p>
]]></content:encoded></item></channel></rss>